← Back to blog

Pricing a catalogue against a store you do not control

Published on
4 mins read
--- views

If you resell, your prices are a function of someone else's prices. That sounds like an integration problem. It is really a caching problem, and getting the caching wrong is how a shop ends up either lying about prices or falling over when the upstream store has a bad afternoon.

Why the two obvious approaches fail

Fetch on page load. A catalogue page showing thirty products becomes thirty outbound HTTP calls. Page load is now bounded by the slowest one, and when the upstream store is slow your shop is slow. When it is down, your shop is down. You have taken a dependency you cannot fix and put it directly in front of every visitor.

Export nightly. No latency risk, but the shop is wrong for up to twenty-four hours. Upstream sales start and end without warning. A customer who arrives after a sale ended pays a price you no longer honour, or you eat the difference on thousands of orders.

Neither is a tuning problem. Both are wrong in kind: one couples availability, the other couples freshness.

Cache with a short life

The middle path is unremarkable and correct: fetch on a schedule, store the result, and serve from storage. In WordPress that is a transient with an expiry.

What makes it work is that the page never triggers a fetch. Rendering reads the cache and only the cache. If the value is missing the page shows the last known price or hides the product; it does not go and ask. The refresh happens on cron, out of band, where a slow upstream response costs nobody anything.

Freshness becomes a dial rather than a property of the architecture. A catalogue that changes daily gets a daily refresh; one that swings hourly gets an hourly one. You are choosing how wrong you are willing to be, which is the honest form of this decision.

Ask for every region, then decide

Here is the part that is specific to resale. The same product often has a different price in every regional storefront, so "the upstream price" is not one number. It is a set.

Collapsing that set into one number is a business decision, not a technical one, and it changes. Take the lowest legitimate regional price. Take a fixed region because of tax rules. Take the median plus a margin. Any of these can be right, and which is right changes with the market and the law.

So put it behind an interface:

interface IProductPriceStrategy
{
    public function choose(array $pricesByCurrency): Price;
}

final class ChooseLowestPriceStrategy implements IProductPriceStrategy { /* … */ }

The strategy is the only place that knows the rule. Swapping it is one class, not a hunt through the codebase for every place a price was computed. This matters more than it looks: pricing rules are exactly the kind of logic that gets copy-pasted into a template, a REST endpoint and an export script, and then diverges.

Type the upstream responses

Parse into typed DTOs rather than reading array keys inline:

$prices = GetPricesResponse::fromArray($body);   // throws if the shape changed

An upstream API that renames a field will otherwise give you null, which becomes 0, which becomes a product priced at zero on a live shop. The failure you want is a loud exception during the scheduled refresh, where nobody is watching but the log. The failure you get without DTOs is a silent one, discovered by a customer.

What this costs

The cache is a second source of truth, and second sources of truth drift. A product removed upstream stays in your catalogue until the next refresh notices. That needs handling explicitly: either the refresh reconciles deletions, or products carry a last-seen timestamp and stale ones are hidden.

There is also a cold-start problem. An empty cache after a deploy means every product is unpriced until the first refresh completes. Warming it as part of the deploy is easy to forget and obvious in production.

The general rule

When you depend on a system you do not control, decide separately how fresh you need to be and how available you need to be. Fetching inline couples both to the other system. Caching lets you buy availability with staleness, and the exchange rate is a number you get to choose.

Open for contract collaboration

I am available for contract-based collaboration. If you have an interesting project idea, schedule a call via Calendly.

Schedule a 30-min call