A store that collected data without consent
ALU-GUM is a family business from Sulmierzyce, trading since 2007. Around six thousand wheels in stock: genuine OE rims for Renault, Dacia, the PSA group, Volkswagen, BMW and Jaguar, steel wheels, tyres and complete wheel sets. A real warehouse, real customers, sales running through several channels at once.
And an online store that could not carry any of it. Built on Elementor, loaded with plugins, with a cookie consent banner that was pure decoration. Google Analytics fired regardless, before anyone clicked "accept".
This case study follows a project that kept growing month after month. It started with a compliance audit, went through a new store and five markets, and ended with the store taking over the role of an external sales management system: prices, Allegro orders, shipping and payments are now run from a single panel.
The audit: most urgent first
We started with a live review of the public code, sitemaps, legal pages and structured data. Not with a visual design, but with the question of what could hit the company as soon as tomorrow.
The Google Analytics script loaded as an ordinary executable tag, with no blocking and no Consent Mode. Data was collected before the user consented. That is a real GDPR risk, and since 2024 the absence of Consent Mode v2 also cuts off remarketing and conversion measurement in Google Ads.
The rest of the list was just as concrete. The cookie policy was a template, with no cookie names and no lifetimes. The accessibility statement existed as an empty URL, with no date, no conformance level and no contact. Next to the returns form sat the word "DOWNLOAD", except with no link behind it, as plain text.
On top of that came a layer invisible to the customer but expensive for the store: three H1 headings per page, homepage structured data describing the store as a blog article, products without a brand or part number, and customer reviews never wired into search engine data.
Analytics started before cookie consent
Accessibility statement: an empty page
Returns form: a "DOWNLOAD" label with no file
Three H1 headings on a single page
Homepage described in structured data as an article
Products without a brand or manufacturer part number
One language, one currency
Elementor and 31 stylesheets
Prices, stock and Allegro orders in an external system
Custom consent mechanism, analytics starts only after acceptance
Accessibility statement and cookie policy with a cookie table
Interactive returns form with a privacy notice and a consent checkbox
One H1 per page, keyboard navigation, skip link
Store described as an auto parts store, products with brand and part number
Brand and manufacturer part number filled in on nearly three thousand items
Five languages and four currencies
Custom theme, two stylesheets
The store as the single source of prices, with its own Allegro and shipping integration
The decision: no patching, a new foundation
The client received two quotes. The first covered removing the legal risk and nothing more. The second, the one they chose, meant throwing out Elementor and building the store from scratch while keeping the catalogue and the orders. We did not migrate data, we replaced the entire presentation and logic layer around it.
Audit and concept
A review of the actual state and a clickable ten-screen concept, approved before the first line of production code
Consent and measurement layer
A custom consent mechanism with Consent Mode v2 instead of a third-party plugin. A tag container and a full e-commerce data layer
A theme from zero
A classic PHP theme with Tailwind and no page builder, from the homepage to checkout and the customer account
Business rules in code
Selling in sets, wheel add-ons, a carrier and payment matrix. What used to live in the owner's head became a rule the store enforces on its own
Migration to new hosting
Moving the store off the old provider, repointing the domain, mail and certificates, with no break in sales
Expansion and automation
Five languages, four currencies, cross-border sales rules, a product feed in Google, a shopping assistant and ad campaigns
Leaving BaseLinker
Importing the client's price list, aligning Allegro prices and cutting the external system off in three places. From that day the store is the source of prices and product data
An integration hub inside the store
Allegro via API: price and stock sync, orders in the store panel, listing offers from the product page. Plus shipping through Polkurier, a packing printout and tracking numbers sent back to the buyer
Cookie consent done honestly
We replaced the third-party consent plugin with our own, because in Consent Mode v2 the order matters in which things reach the page head. A plugin that does it its own way can break measurement despite a correct-looking banner.
- Denied by default for analytics and marketing, set before anything loads
- The remembered user choice read before the tag container starts
- Without consent the data goes out anonymised, after consent in full, and that is the difference between legal and illegal measurement
The e-commerce data layer was built on that foundation: product view, add to cart, checkout start and purchase together with the amount, currency and order number. One source for analytics and ads.
A new store instead of Elementor
The theme was built from scratch, on tokens taken from the approved concept. The numbers show it, because removing the page builder stripped around 470 kilobytes of raw HTML off the page, before styles and scripts are even counted.
Accessibility went up from eighty-eight to ninety-five points. One H1 per page, a skip link, correct contrast, keyboard support. This is not cosmetics, it is a requirement of the European Accessibility Act, which has applied to online stores since June 2025.
A product page that sells the set
Wheels are sold in sets and the store finally knows it. The product page has a configurator: the customer picks the number of items, adds tyres, pressure sensors and fitting, and the total recalculates live. The owner's business rule was written into the code, including recognising when an offer covers a single item and when it covers a full set.
- A search that understands queries like "clio 17" or "5x114.3" and turns them into filters
- Selection by specification: diameter, width, bolt pattern, centre bore and offset
- A condition filter separating new wheels from used ones, built on data from the whole catalogue
Delivery and payment without calling the customer
The rules for shipping wheels used to be tribal knowledge. We turned them into an automation that does not let an impossible order through.
- Wheels ship by courier only, the parcel locker option disappears on its own
- Small items can go both by courier and to a parcel locker
- Cash on delivery is unavailable for parcel lockers, prepayment remains
- Cross-border shipping goes with international couriers only, with a minimum cost threshold
- Outside Poland cash on delivery is not offered at all
- Online payments through Przelewy24, with BLIK and cards, alongside bank transfer and cash on delivery
Five languages and four currencies
The store was meant to go beyond Poland, so it got five language versions: Polish, German, Czech, Slovak and Romanian, each in its own URL directory. We translated not only the interface but the catalogue: over three thousand products times four languages is around fifteen thousand product entries, plus menus, categories, pages and the full set of legal content.
Once multilingual support was switched on, the filters stopped working on the foreign versions. There were two causes: the translation plugin also covered the technical wheel attributes, and the translated products never made it into the table WooCommerce reads prices and stock from. Fixing both layers restored the filters in every language.
On top of that came four currencies: zloty, euro, Czech koruna and Romanian leu, with rates pulled automatically from the National Bank of Poland. The currency follows the language, and the customer can change it manually with the choice remembered.
Cross-border sales with its own rules
Prices for foreign markets get a fixed percentage markup, applied before the currency is converted, so the order of operations does not distort the amount. Shipping outside Poland goes only with selected international couriers, with a minimum amount converted into the customer's currency. All thresholds are set from the store admin panel, without touching the code.
A catalogue ready for marketplaces and Google
ALU-GUM does not sell only on its own website. Allegro, Poland's largest marketplace, is the main channel, and in the first phase of the project prices and stock for the whole catalogue lived in BaseLinker, which fed the store and the marketplaces. Our job back then was to clean up the data those channels consume.
- Manufacturer and manufacturer part number filled in on nearly three thousand items, because without them marketplaces and Google Shopping rank the offer lower
- The part number automatically assigned as the product identifier on the store side
- The catalogue sent to Google Merchant Center via API, over three thousand offers, with images and labels for bid control
- Prices for the Google feed always in the domestic version, regardless of the currency the visitor sees
Once multilingual support was switched on, the store's API started returning products from all languages at once. The script sending the catalogue to Google duplicated offers several times over, which ended in thousands of rejected items. We found the cause, cleaned up the duplicates and rebuilt the script so it filters by language and stops when the data looks suspicious. Any program reading data from a multilingual store has to state the language explicitly.
When the middleman started breaking prices
At the end of September the owner called with a problem that hit the very core of sales: prices and stock had drifted across the whole catalogue, in the store and on Allegro at the same time. Staff were correcting prices by hand, offer by offer. The decision was made during that same call: drop BaseLinker and make the store the source of truth for prices and product data.
Leaving a system that every sale passed through could not mean a single day of wrong prices. We did it in an order where every step could be checked and rolled back.
A price list to fill in
An export of all products to a spreadsheet where the client enters only the changes. A separate tool checked the returned file: prices off by more than 25 percent, the same item under different prices, cloned offers, rows with no price
Import with a dry run
First a change plan with the old and new price of every item, which doubles as a rollback copy. Then a test on one product in five languages, and only then the full run
Aligning Allegro with the store
Allegro offers paired with store products. The script changed only unambiguous pairs. Cases like "price per wheel versus price per set" went to a spreadsheet for the client to confirm
Cutting off in three places
Store access, Allegro access and the automations in the panel. After each step another dry run on both sides, which has to show zero changes left to make
The price history the store has kept since July for the EU Omnibus Directive proved its worth: every change is recorded, including the whole price list import, and the lowest price from the previous 30 days is shown next to every discount.
Allegro run from the store
The client set a clear goal: staff should not have to log in to Allegro. Offers, prices and orders belong in one panel. Instead of another middleman we wrote our own plugin connecting WooCommerce to the Allegro REST API. There were already close to three thousand active offers, so the first task was not listing but permanently linking existing offers to products.
- Authorisation on the client's account with a code to click, no redirects and no password sharing
- 2,627 offer and product pairs stored on both sides: the offer number on the product, the product ID in the offer's external reference
- Price and stock sync every five minutes, strictly from the store to Allegro, sending only the differences
- A price change of more than 25 percent is not sent automatically, it waits for confirmation in the panel
- Allegro orders in the WooCommerce panel every ten minutes, marked as Allegro, with payment status and pickup point
- Statuses in both directions: an order completed in the store is marked as shipped on Allegro, a cancellation on Allegro cancels it in the store
Allegro rejected the external reference on 145 offers. The cause was not our code but product safety regulations: offers marked as "new" carried an old declaration the marketplace no longer accepts on edit. The link exists in the store for every pair, and the list of offers needing responsible manufacturer data went to the client's staff.
Listing from the product page
A product page with no offer has a "List on Allegro" button. The store takes the Allegro category, shipping rates and returns terms from the most recent linked offer in the same category, maps wheel parameters by attribute name, uploads up to sixteen images and builds the description as alternating text and image sections. When a required parameter is missing or a value is outside the Allegro dictionary, nothing is sent and staff get a list of what is missing. The offer is created as a draft and goes live only after clicking "Activate". The first live draft passed Allegro validation with no errors and no warnings.
From printout to tracking number
Shipping labels used to come from the external system, so once it was switched off the store had no carrier integration at all. We built the shipping flow from a conversation with the people who pack wheels every day.
- A packing printout: one order per A4 sheet with a photo of each item, part number, customer notes and payment status, instead of copying orders into a notebook
- A new "Packed" status between processing and shipping
- The number of parcels calculated by the store: wheels up to 18 inches in two parcels, larger ones in four, with exceptions by part number set in the panel
- Dispatch through the Polkurier broker: wheels by DPD courier with pickup booking, small items by InPost
- After dispatch the order closes itself, the customer gets the number and a tracking link, and for Allegro orders the number also goes to Allegro
In the previous flow a set worth PLN 2,000 shipped in two parcels got cash on delivery on each of them, PLN 4,000 in total. The customer refused the delivery, the parcels came back and staff fixed it by hand. Now a set goes as one multi-parcel shipment with a single cash on delivery amount for the whole order.
The flow is deployed and checked against live quotes and pickup slots from the client's account. The first production label will be its final test.
More channels
The hub inside the store does not stop at Allegro. The Google Merchant Center feed has read data from the store from day one, and the store is also connected to the Ceneo price comparison site. On the same foundation, with the same rules (one direction of data, dry runs, a log), we are preparing further marketplaces: Ovoko and eMAG. Each of them gets data from one place, so a new channel does not mean a new source of errors.
Felek, a shopping assistant with no subscription
The client saw a chat at a competitor's store run by a rented service and wanted the same. We did the maths and proposed something else: our own assistant, with no language model and no subscription fees.
Felek recognises the intent of a question, copes with typos and inflection, understands questions in five languages and searches the store catalogue. Ask it about a "golf" or a "passat" and it maps the name to a car model. Questions about delivery, returns, payments and cross-border sales are answered with data taken straight from the store settings, so it cannot drift from reality.
Questions Felek could not answer land in the admin panel with a repeat counter, and the owner adds an answer with one click. It learns without machine learning: the second version was built from exactly that list.
Advertising that knows what sells
The Google Ads campaigns launched only once the store could measure sales down to the last zloty, because without that advertising is spending you know nothing about.
- Three campaigns: search by car make, shopping fed by the catalogue, and brand defence
- Five hundred and four keywords in match types that limit accidental traffic
- Seventy-four negative keywords from day one, including repair, straightening, buying up and rental
- Purchase tracking passing the amount, currency and order number, so the report shows revenue, not clicks
We started with manual bidding on clicks, because a conversion-optimising strategy needs sales history the account did not have. After the first 30 days the audit showed that the shopping campaign sells and loses the most auctions to budget. Before the season we moved budget to where the purchases are, with every change made through the API and a copy of the previous state kept.
An outage that taught us something
At the end of August the store stopped responding for almost a day. The cause was a configuration workaround inherited from the migration, which the host finally killed. Instead of reviving it, we moved the store to the current, system-level PHP version. A week later a side effect surfaced: after a series of failed requests during the outage, BaseLinker had paused order import on its own and did not resume it when the store came back, even though the store worked correctly. Today Allegro orders go straight into the store, and every integration call leaves a trace in a log visible in the panel.
Technology
WooCommerce
PHP 8.5
MariaDB
Tailwind CSS 4
Classic theme, no page builder
WooCommerce REST API
Polylang Pro
Multi-currency with NBP rates
Allegro REST API
OAuth 2.0 device flow
Polkurier (DPD, InPost)
Przelewy24
Ceneo
Google Tag Manager
GA4 with Consent Mode v2
Google Merchant Center Content API
Google Ads API
Schema.org JSON-LD
WP-Cron
Python
Docker
Results
What this project teaches
The store was not broken because someone lacked the will. It was broken because each new element was bolted onto the previous one until the whole thing stopped holding together: a page builder on a plugin, a plugin on a plugin, cookie consent as a picture to close. Fixing it meant reversing the order: legal and measurement foundations first, then a fast presentation layer, and only at the end expansion and advertising.
Thanks to that order, every next stage could be added without rebuilding the previous one. Multilingual support went onto a finished theme. Currencies went onto multilingual support. Cross-border sales went onto currencies. Advertising went onto measurement that had been in place since the first week of the project.
The same principle held for the biggest change. Leaving BaseLinker took less than two weeks, because the store already had clean data, a price history and tools with dry runs. Integrations stopped being a black box at an external vendor and became code with tests, a log and a single direction of data flow. The next marketplace is now just another consumer of the same data, not another place where prices can drift.