Digital bedriftshandel (B2B) stiller strenge tekniske krav til arkitekturen bak nettbutikken. Profesjonelle innkjøpere forventer avtalefestede kundepriser, minimumsbestillinger (MOQ) og oversiktlige rabattstiger ved større innkjøpsvolum. Samtidig krever søkemotorer og handleassistenter basert på kunstig intelligens nøyaktige og maskinlesbare data om enhetspriser og volumtrinn. Eventuelle avvik mellom prisene i datafeeden og beløpene i kassen fører til avviste produkter i Google Merchant Center.
For å etablere et stabilt system for trappepriser i WooCommerce må man forlate utdaterte mønstre for datalagring. Det kreves skreddersydde relasjonelle tabeller, minneeffektive eksportrutiner for XML og NDJSON, samt presis semantisk merking i henhold til Schema.org.
Krav i Google Merchant Center for enhetspriser og volumstiger
Google Merchant Center håndhever detaljerte regler for varer som selges etter vekt, volum, lengde eller i flerpakninger. I EU, EFTA-landene (Norge inkludert), Storbritannia, Australia og New Zealand krever Google enhetspris for produkter der lokale regler sier at den skal vises, slik at både bedriftskunder og forbrukere kan sammenligne priser basert på en standardisert referanseenhet (for eksempel pris per kilo, liter eller kvadratmeter).
De sentrale attributtene i feedspesifikasjonen er:
unit_pricing_measure: Den faktiske mengden eller dimensjonen i salgsenheten (for eksempel25kg,500mleller100ct). Denne verdien forteller systemet hvor mye varen faktisk inneholder.unit_pricing_base_measure: Den faste referanseenheten for prissammenligning (for eksempel1kg,100mleller1ct). Google bruker denne nevneren til å regne ut den synlige enhetsprisen.auto_pricing_min_price: Den laveste prisen Google kan sette ned et produkt til i automatiske rabatter og dynamiske kampanjer.loyalty_program: Gruppeattributt for medlemspriser, medlemsfrakt og lojalitetspoeng. Det er det eneste stedet for priser som avhenger av medlemskap i et program; det finnes ingen egen attributt for engrostrinn.
Feilaktige måleenheter eller valutafeil fører til kritiske feilmeldinger i Merchant Center. Dersom et industrielt smøremiddel selges i en 20-liters dunk til 1 500 NOK, må unit_pricing_measure settes til nøyaktig 20l og unit_pricing_base_measure til 1l, slik at Google kan beregne og vise enhetsprisen på 75 NOK per liter.
Hvordan sende trappepriser for B2B i en feed til Google Merchant Center
Googles produktdataspesifikasjon har ingen attributt for volumtrinn. Hver vare i feeden har én price, og reglene for attributtet price bestemmer hvilket tall det skal være. Gjelder det et minstekvantum, er prisen i feeden prisen for dette minstekvantumet: Google sier at man ikke skal sende stykkprisen når kunden ikke kan kjøpe bare én. For en flerpakning eller et sett som forhandleren selv har satt sammen, er prisen totalen for hele pakken.
Da står man igjen med tre farbare måter å vise en volumstige på:
- Egne varer per pakningsstørrelse. Hver størrelse (spann, kartong, pall) blir en egen vare med egen
id, egenpriceog egenunit_pricing_measure, og alle deler sammeitem_group_id. Dokumentasjonen for unit_pricing_measure viser dette mønsteret med ris i poser på 5, 10 og 12 pund, hver med en basismengde på 1 pund, slik at enhetsprisen viser kjøperen hvilken pakning som er billigst per vektenhet. - Attributtet
multipack. Ifølge siden om multipack gjelder det pakker som forhandleren har satt sammen av like produkter, og det er påkrevd for gratis oppføringer når du lager en slik pakke. For en pakning definert av produsenten sender du ikkemultipack, og antallet uttrykkes iunit_pricing_measure. - Pristabellen blir værende i nettbutikken. Rabatter over minstekvantumet ligger i WooCommerce og på produktsiden, mens feeden sender inngangsprisen. Dette alternativet mangedobler ikke varenumrene og holder seg konsistent så lenge landingssiden viser den samme inngangsprisen.
For selve enhetsprisen må begge attributtene bruke samme enhetstype, og unit_pricing_base_measure godtar bare bestemte verdier: 1, 2, 4, 8, 10 eller 100 av en enhet, i tillegg til 75cl, 750ml, 50kg og 1000kg (unit_pricing_base_measure). Mengden kan ha høyst to desimaler. For Storbritannia leser Google imperiske enheter som amerikanske, så metriske verdier er det trygge valget. Har en vare både unit_pricing_measure og en energiklasse, viser Google bare energiklassen.
Hvilke regler for landingsside og kasse som gir avviste B2B-produkter i Merchant Center
De fleste B2B-avvisninger skyldes avstanden mellom feeden og det kjøperen ser. Reglene for attributtet price inneholder kravene som kolliderer med vanlige engrosoppsett:
- Prisen i feeden må samsvare med prisen på landingssiden og i kassen, i valutaen til mållandet, og “se pris i handlekurven” er ikke tillatt.
- Alle brukere i mållandet må kunne kjøpe til den oppgitte prisen. Krav om konto er bare tillatt når registreringen er gratis og enkel, noe som utelukker feeder bygget på priser som bare godkjente bedriftskontoer ser.
- I land som Tyskland, Frankrike og Storbritannia inkluderer prisen merverdiavgift, og Google bruker dette også på engrostilbud. I USA og Canada holdes avgiftene utenfor prisen.
- En minste ordreverdi er ikke en del av prisen. Den settes i fraktinnstillingene.
Avtale- og medlemspriser har sin egen kanal. Google fastslår at medlemspriser ikke skal stå i price eller sale_price. De hører hjemme i gruppeattributtet loyalty_program, med underattributter som program_label, tier_label, price, loyalty_points og member_price_effective_date. Programmet må først settes opp i Merchant Center, medlemsprisene må være synlige for medlemmene i nettbutikken, og Google viser medlemspriser i annonser bare når rabatten er minst 5 prosent eller 5 valutaenheter. Googles eksempler er kundenivåer som sølv og gull, ikke verifiserte bedriftskontoer, så en B2B-butikk må sjekke om engrosprislisten i det hele tatt passer i den modellen.
En tidsbegrenset kampanje på et trinn hører hjemme i sale_price, som må være lavere enn price. Ordinær pris sendes fortsatt i price, landingssiden viser begge prisene, kassen viser bare kampanjeprisen, og sale_price_effective_date avgrenser perioden; uten den gjelder kampanjeprisen umiddelbart. auto_pricing_min_price er et annet verktøy: attributtet for minstepris er påkrevd for automatiske rabatter og dynamiske kampanjer og må ligge mellom varekostnaden, hvis den oppgis, og 95 prosent av ordinær pris.
Begrensninger i wp_postmeta og en egen tabell for prisstiger
Standardmodellen i WordPress er bygget på Entity-Attribute-Value (EAV), der alle egenskaper for produkter og varianter lagres i tabellen wp_postmeta. For enkle forbrukerbutikker fungerer dette tilfredsstillende. I avanserte B2B-miljøer, hvor en enkelt vare kan ha 5 rabattrinn for 8 ulike kundegrupper, oppstår det fort alvorlige ytelsesproblemer.
Å lagre 40 prisregler for ett enkelt produkt krever 40 rader i wp_postmeta. Med et sortiment på 30 000 varer og 100 000 varianter vokser tabellen til titalls millioner oppføringer. Hver spørring i varekatalogen krever gjentatte LEFT JOIN-kjøringer, noe som skaper låsninger i MySQL og fører til treg serverrespons.
High-Performance Order Storage (HPOS) løser ikke dette alene. Ifølge WooCommerce sin HPOS-dokumentasjon flytter det ordrer og ordreadresser til fire egne tabeller (wc_orders, wc_order_addresses, wc_order_operational_data og wc_orders_meta), mens produktene blir liggende i innleggstabellene. Det HPOS gir, er mønsteret: data med kjent struktur får en egen tabell med indekser som passer spørringene. For prisstiger betyr det en separat tabell med sammensatte indekser:
-- Egen relasjonell tabell for raske B2B-trappepriser
CREATE TABLE IF NOT EXISTS wp_wc_b2b_price_tiers (
tier_id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
product_id BIGINT UNSIGNED NOT NULL,
customer_group VARCHAR(32) NOT NULL DEFAULT 'all',
min_quantity INT UNSIGNED NOT NULL DEFAULT 1,
max_quantity INT UNSIGNED DEFAULT NULL,
price_cents BIGINT NOT NULL,
currency CHAR(3) NOT NULL DEFAULT 'NOK',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_product_lookup (product_id, customer_group, min_quantity),
INDEX idx_pricing_search (min_quantity, max_quantity)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;Med en sammensatt indeks på (product_id, customer_group, min_quantity) blir oppslaget på produkt, kundegruppe og kvantum én enkelt intervallesing i indeksen i stedet for en kjede av koblinger mot wp_postmeta. Ved å fjerne belastningen fra wp_postmeta frigjøres serverressurser til andre krevende oppgaver.
Les mer om hvordan du knytter nettbutikken sammen med sentrale forretningssystemer i vår guide om woocommerce erp integrasjon.
Minneeffektiv strømming av datafeeder i XML og NDJSON
Regelmessig eksport av titusenvis av B2B-produkter med tilhørende rabattstiger til Google Merchant Center krever en robust løsning. Å laste hele varekatalogen inn i minnet samtidig via vanlige funksjoner som wc_get_products() fører fort til at serveren krasjer på grunn av overskredet minnegrense (memory_limit).
En driftssikker eksportmotor benytter databasemarkører i kombinasjon med PHP-generatorer (yield). Dette gjør det mulig å skrive produktdata fortløpende til disk eller strømme dem til skylagring i faste bolker:
<?php
/**
* Minneeffektiv generator for Google Merchant Center produktfeeder
*/
declare(strict_types=1);
namespace WPPoland\B2BFeedsNO;
use Generator;
use wpdb;
class StreamedProductFeedGenerator
{
private wpdb $db;
public function __construct(wpdb $db)
{
$this->db = $db;
}
public function streamCatalogBatch(int $batchSize = 500): Generator
{
$offset = 0;
while (true) {
$query = $this->db->prepare(
"SELECT p.ID, p.post_title, t.min_quantity, t.price_cents, t.currency
FROM {$this->db->posts} p
INNER JOIN wp_wc_b2b_price_tiers t ON p.ID = t.product_id
WHERE p.post_type IN ('product', 'product_variation')
AND p.post_status = 'publish'
AND t.customer_group = 'wholesale_tier_1'
ORDER BY p.ID ASC
LIMIT %d OFFSET %d",
$batchSize,
$offset
);
$rows = $this->db->get_results($query, ARRAY_A);
if (empty($rows)) {
break;
}
foreach ($rows as $row) {
yield $this->formatGoogleXmlNode($row);
}
$offset += $batchSize;
}
}
private function formatGoogleXmlNode(array $product): string
{
$price = number_format($product['price_cents'] / 100, 2, '.', '');
return sprintf(
"<item>\n <g:id>%d</g:id>\n <g:title><![CDATA[%s]]></g:title>\n <g:price>%s %s</g:price>\n <g:min_handling_time>1</g:min_handling_time>\n</item>\n",
$product['ID'],
htmlspecialchars($product['post_title'], ENT_QUOTES, 'UTF-8'),
$price,
$product['currency']
);
}
}Siden bare én bolk på 500 rader ligger i minnet om gangen, avhenger minnebruken av bolkstørrelsen og ikke av størrelsen på katalogen. Ved å kjøre eksporten via bakgrunnsoppgaver i WP-CLI unngår man å belaste kundene som handler i butikken.
For å sikre at eksportfiler og API-er er beskyttet mot uautorisert innsyn, anbefales en grundig sikkerhetsrevisjon for wordpress.
Semantisk oppmerking med PriceSpecification og unitCode i schema
For at søkemotorer og KI-agenter skal tolke trappepriser og enhetspriser riktig, må dataene merkes nøyaktig i sidens kildekode. Schema.org beskriver enhetspriser med typen UnitPriceSpecification, som legges inn i et tilbud.
Det anbefales å bruke offisielle enhetskoder fra UN/CEFACT (som KGM for kilo, LTR for liter, MTK for kvadratmeter og C62 for stykk):
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Strukturlim av polyuretan, 5 kg spann",
"sku": "ADH-IND-500",
"offers": {
"@type": "Offer",
"price": "2600.00",
"priceCurrency": "NOK",
"availability": "https://schema.org/InStock",
"priceSpecification": {
"@type": "UnitPriceSpecification",
"price": "2600.00",
"priceCurrency": "NOK",
"referenceQuantity": {
"@type": "QuantitativeValue",
"value": 5,
"unitCode": "KGM",
"valueReference": {
"@type": "QuantitativeValue",
"value": 1,
"unitCode": "KGM"
}
}
}
}
}Googles dokumentasjon for selgeroppføringer snevrer inn det Schema.org tillater. Selgeroppføringer trenger et Offer, fordi selgeren må være den som selger varen; AggregateOffer godtas bare for produktutdrag. Enhetsprisen legges i priceSpecification som UnitPriceSpecification, der referenceQuantity er mengden som selges for prisen og valueReference er basismengden, altså motstykkene til unit_pricing_measure og unit_pricing_base_measure. I eksempelet inneholder spannet 5 kg, så prisen på 2 600 NOK tilsvarer 520 NOK per kilo. Den eneste priceType Google støtter, er StrikethroughPrice, medlemspriser bruker validForMemberTier sammen med en ordinær pris, og en spesifikasjon som kombinerer begge blir ignorert. Siden nevner ikke eligibleQuantity, så man bør ikke regne med at trinn over inngangsprisen vises i Googles resultater; oppmerkingen bærer inngangsprisen, den samme som feeden.
Hurtigbufring på serveren ved innloggede engroskunder
Fullside-mellomlagring (som FastCGI-cache i Nginx eller Redis) kommer til kort i B2B-butikker der innloggede kunder har egne prisavtaler. Å skru av bufferen for alle påloggede brukere skaper tung belastning på serveren og gir uakseptabelt lange svartider.
En mer fremtidsrettet modell benytter hybrid kantskystrategi:
- Felles lagring av produktskallet: Statisk innhold som produkttekster, spesifikasjoner og bilder hurtigbufres globalt på CDN-nivå.
- Asynkron innlasting av priser: Når en innlogget kunde åpner siden, henter nettleseren kundens avtalepriser via en lett REST-forespørsel.
- Klientoppdatering i sanntid: Et lett JavaScript-skript oppdaterer prisvisningen i nettleseren uten at den felles bufferen må forkastes.
For bedrifter som krever maksimal fart og fleksibilitet, gir frakoblede løsninger store fordeler. Lær mer om våre leveranser innen headless wordpress.
Sanntidssynkronisering mot ERP og transaksjonssikkerhet
At prisene i nettbutikken stemmer overens med Google Merchant Center avhenger av hvor ofte systemet oppdateres mot forretningssystemet (ERP). Når råvare- eller innkjøpspriser endres sentralt, må dette slå direkte gjennom til nettbutikken og videre til annonsekanalene.
Integrasjonen bør bygges på køhåndtering i Action Scheduler og hendelsesstyrte webhooks. Hver prisendring oppdaterer tabellen wp_wc_b2b_price_tiers og sender en direkte oppdatering gjennom Google Merchant API, uten at man må vente på den nattlige eksporten.
En toveis synkronisering med transaksjonsbuffere beskytter databasen mot låsninger ved store ordremengder. Avvik mellom faktisk lagerbeholdning og nettbutikken loggføres automatisk og varsler driftsansvarlige umiddelbart.
Hva Google for WooCommerce kobler og hva som krever egen feed
Den offisielle utvidelsen Google for WooCommerce dekker en del av dette oppsettet. Attributtkoblingen støtter 15 Google-attributter, blant dem Multipack, Is Bundle, GTIN, MPN og Brand. En regel kan gjelde alle kategorier, alle unntatt valgte kategorier eller én enkelt kategori. Reglene fyller bare tomme attributter, og verdier satt i Google-fanen på et produkt går foran. Reglene leser kun globale attributter, så attributter opprettet på ett enkelt produkt må gjøres globale først.
Mengden for enhetspris står ikke på denne listen. En butikk som selger etter vekt, volum eller areal trenger derfor verdier per produkt eller en egen feed- eller API-integrasjon for unit_pricing_measure og unit_pricing_base_measure. Det samme gjelder egne varer per pakningsstørrelse generert fra en egen pristabell, som utvidelsen ikke kjenner til.
Transportlaget endret seg i 2026. Google oppgir at Content API for Shopping ble avviklet 18. august 2026, at forespørsler gradvis begynner å feile fra 1. september 2026, og viser til Merchant API. Google for WooCommerce gjorde overgangen i versjon 3.8.1. En egen integrasjon skriver produkter gjennom ProductInput-ressurser med insert, patch og delete, leser de ferdig behandlede Product-ressursene og kan bare skrive til datakilder av typen API, slik produktveiledningen for Merchant API beskriver.
Automatisert testing og kvalitetssikring av prislogikk
I bedriftsmarkedet kan feil i prisberegninger få alvorlige konsekvenser. Dersom prisen som vises i Google Shopping avviker fra det kunden faktisk betaler, risikerer man sanksjoner etter markedsføringsloven samt utestengelse fra Googles plattformer.
Kvalitetssikringen må omfatte automatiserte tester som sjekker prisberegninger ved hver nye versjon:
<?php
/**
* Integrasjonstest for B2B-trappepriser og enhetsberegning
*/
declare(strict_types=1);
namespace WPPoland\Tests\PricingNO;
use PHPUnit\Framework\TestCase;
class TieredPricingCalculationTest extends TestCase
{
public function testVolumrabattKorrektBeregnet(): void
{
$kalkulator = new \WPPoland\Pricing\TieredPriceCalculator();
// Tester vare med standardpris 520 NOK og rabattpris 390 NOK ved 10 stk
$enhetsprisEnkelt = $kalkulator->resolveEffectivePrice(
productId: 2048,
quantity: 3,
customerGroup: 'wholesale_tier_1'
);
$this->assertEquals(52000, $enhetsprisEnkelt, 'Kvantum under grensen skal ha standardpris.');
$enhetsprisVolum = $kalkulator->resolveEffectivePrice(
productId: 2048,
quantity: 15,
customerGroup: 'wholesale_tier_1'
);
$this->assertEquals(39000, $enhetsprisVolum, 'Kvantum over 10 enheter skal fa rabattert pris.');
}
public function testMvaBeregningOgEksportAttributter(): void
{
$feedValidering = new \WPPoland\B2BFeedsNO\FeedAttributeValidator();
$gyldigEnhet = $feedValidering->validateUnitPricingAttributes(
measure: '25kg',
baseMeasure: '1kg'
);
$this->assertTrue($gyldigEnhet, 'Gyldige metriske enheter ma passere valideringen.');
$ugyldigEnhet = $feedValidering->validateUnitPricingAttributes(
measure: '25sekker',
baseMeasure: 'palle'
);
$this->assertFalse($ugyldigEnhet, 'Ustandardiserte enheter ma avvises for a hindre feil i Google Merchant Center.');
}
}Dette sikrer at avrundingsfeil, valutaomregninger og 25 prosent merverdiavgift (MVA) kontrolleres automatisk før dataene sendes videre til eksterne systemer.
Teknisk sjekkliste for Google Shopping B2B
Gå gjennom følgende punkter før lansering av B2B-kampanjer:
- Kontroll av måleenheter: Sørg for at
unit_pricing_measureogunit_pricing_base_measurebare bruker enheter fra Googles støttede liste og en tillatt basisverdi. - Nøyaktighet i handlekurven: Bekreft at overgang til et høyere volumtrinn oppdaterer enhetsprisen i kassen automatisk.
- GTIN- og MPN-numre: Kontroller at alle forpakningsstørrelser har unike og godkjente GS1-strekkoder.
- Ytelsestesting av databasen: Kjør stresstester med mange samtidige spørringer mot pristabellene.
- Validering av Schema-data: Test nettadressene i Googles verktøy for rike resultater for å utelukke feil i
UnitPriceSpecification. - Minnekontroll ved eksport: Påse at toppforbruket av PHP-minne under feed-genereringen holder seg stabilt selv om katalogen vokser.
- Overvåking av feillogger: Sett opp automatiske varsler ved avviste produkter fra diagnostikken i Merchant Center eller Merchant API.
- Sikkerhet og kryptering: Krev TLS 1.3 og sikre tilgangsnøkler for alle API-er som håndterer prisdata.
- B2B-fraktregler i Merchant Center: Definer egne retningslinjer for pallefrakt og minimumsverdier for ordre.
- Fallback-mekanisme ved API-brudd: Implementer en sikkerhetsbryter som serverer faste listepriser dersom forbindelsen til ERP-systemet midlertidig faller ut.
En velfungerende B2B-løsning krever grundig samspill mellom databaseoppsett og søkemotorenes krav. Dersom du ønsker en teknisk gjennomgang av dine produktfeeder, er du velkommen til å kontakte oss via vår kontaktside.







