It's all 'bout the money. It's all 'bout the dum dum da da dum dum. If you were rocking it in the late 90s, you might remember this Meja lyric.
Now, more than 20 years later, not much has changed. Well, maybe the dum dum da da dum dum, but it is, in fact, still all 'bout the money.
When it comes to business, getting the price exactly right is an art in itself, which is why pricing is also one of the four Ps in the legendary "Marketing mix"-model.
Perfecting the price and finding the sweet spot in supply and demand is both a complex and continuous task.
The price P of a product is determined by a balance between production at each price (supply S) and the desires of those with purchasing power at each price (demand D). The diagram shows a positive shift in demand from D1 to D2, resulting in an increase in price (P) and quantity sold (Q) of the product. (Source: Wikipedia).
The complexity of this task doesn't decrease when it comes to B2B prices. Unlike regular B2C pricing, where the price is mostly the same – and visible – to all customers, B2B pricing often has custom prices (individual prices for different customer segments), and the price itself is often hidden behind a login.
There can be various reasons for this. The price may be tailored to the customers' use case and needs. They may have negotiated a better deal. Or the price/discount may have been set by how much money they're spending (e.g., a large company buying 1,000 bags of coffee a month will get a better deal than a company buying 50 bags).
If we take a traditional B2B eCommerce/catalogue site, a common way to handle custom prices would be to have predefined customer segments/groups to which each customer can belong. E.g.
- Segment X
- Segment Y
- Segment Z
This is a relatively simple and easy way to manage it. The rules can be as complex as one wishes, e.g., different discounts on a category/product level, etc. The point is that the price is tied to the customer segment/group and not to the individual customer.
These rules are handled either by the eCommerce system or, especially for larger sites, a PIM system (Product Information Management).
A PIM system can help to greatly optimise the way a company manages and organises its product data. They come in all shapes and sizes but can quickly become both large and expensive.
However, what we're interested in isn't the cost nor the size. It's the speed. We want websites to be blazingly fast, no matter how complex the products are.
Now you're probably thinking: "Naturally, big expensive enterprise solutions offer fast responses with an easy API, right?"
Yeah, about that. There's a reason why, for instance, the banking industry still runs on COBOL (a 60-year-old programming language). Big enterprise solutions aren't famous for running bleeding edge solutions… or cutting edge solutions… or "whatever is 500 meters away from the edge"-solutions.
Please note that we're not saying this is true for all systems. Not at all. But we are saying that there are systems out there that could improve their performance and the APIs they offer.
Luckily there are solutions for this * clears throat *.
Yes, yes. Once again, we're tooting our own horn (but what a horn).
If you know anything about Enterspeed, you know the deal, so we'll make it quick.
We help you decouple your systems, be it CMS, eCommerce, or PIM, by storing your data in our blazing-fast network. Moreover, you can easily transform and combine data from multiple sources using our low-code schema designer to model your content.
But, Enterspeed also shines (sorry, this is the last toot – promise!) by essentially caching complex data like PIM data. You can read more about how we handle Automatic Cache invalidation.
So, the question is, how do we handle showing customer-specific prices without exposing them to other customers? Let's look at one way to do it.
Handling custom prices with Enterspeed
To keep this article short, let's say we've already ingested our data into Enterspeed and modelled the data, which now leaves us with X number of product views available to fetch via our Delivery API.
{
"title": "Hamburger Earmuffs",
"brand": "Frink inventions",
"description": "The tastiest way to keep your ears warm!",
"inStock": true,
"currency": "EUR",
"price": {
"regular": 20,
"sale": 18,
"customer-x": 19,
"customer-y": 18,
"customer-z": 17
}
}
Here is the response we get when we fetch the URL "/products/hamburger-earmuffs". In this example, we have stored all the custom prices in the pricing object.
Now we want to show the correct price to the correct customer – and only show the price if they're authenticated.
Because we have all the information stored in a single view, we can't fetch it client-side since that would reveal all the prices in the GET request. So instead, we'll have to fetch it server-side.
If we use a framework like Next.js, that's really easy to do by using the getServerSideProps – and even easier if you're using their new app directory in Next 13, which uses server components.
So, let's build a function that does that. Then, let's break down what it should do.
- It should fetch the data from the Enterspeed Delivery API
- It should only return a price if a user is authenticated.
- It should check to see if the user belongs to one of the groups on the price object.
- If the user doesn't belong to any of the groups, it should return the regular price.
- If there's a sale price present, it should check to see if it's lower than the customer's price since we don't want to cheat them out of any discounts we're running.
We start by creating a helper function to handle fetching the data from Enterspeed. You can check it out here in one of our demo repos.
const call = async (query) => {
const url = `https://delivery.enterspeed.com/v2?${query}`;
const response = await fetch(new Request(url), {
headers: {
"Content-Type": "application/json",
"X-Api-Key": process.env.ENTERSPEED_PRODUCTION_ENVIRONMENT_API_KEY,
},
});
return response.json();
};
export const getByHandle = async (handle) => {
const response = await call(`handle=${handle}`);
return response.views;
};
export const getByUrl = async (url) => {
const response = await call(`url=${url}`);
return {
...response.route,
...response.meta,
};
};
Next, we create the function that handles the actual product logic. Let's call it getProduct.
An auth provider will handle the authentication, including the user's segment/group. In this example, we assume we have access to:
- If the user is authenticated or not.
- The user segment (if any) the user belongs to.
- The URL of the product they want to view.
Having access to this, we can build a function that looks something like this:
import { getByUrl } from "/lib/enterspeed";
const getProduct = async (url, isAuthenticated, userSegment) => {
try {
const product = await getByUrl(url);
if (!product) {
throw new Error("Product not found");
}
let { price } = product.price;
if (!isAuthenticated) {
price = "Login to see price";
}
if (!userSegment || !price.hasOwnProperty(userSegment)) {
price = price.default;
}
const customerPrice = price[userSegment];
const salePrice = price.sale;
if (salePrice && salePrice < customerPrice) {
price = salePrice;
}
price = customerPrice;
return {
...product,
price,
};
} catch (error) {
console.error(error);
return {
error: error.message,
};
}
};
export default getProduct;
Let's break down step-by-step what the function does:
- Takes three parameters: url (a string), isAuthenticated (a boolean), and userSegment (an optional string).
- Attempts to fetch a product object from the Enterspeed Delivery API using the getByUrl function.
- If the product object is falsy (e.g. null, undefined, etc.), throws an error with the message "Product not found".
- Extracts the price property from the product object and assigns it to a variable.
- If isAuthenticated is false, sets the price variable to the string "Login to see price".
- If userSegment is falsy or the price object doesn't have a property matching userSegment, sets the price variable to the default property of the price object.
- Retrieves the price of the product based on the userSegment
- Retrieves the sale price of the product, if it exists.
- If a sale price exists and it's less than the customer price, sets the price variable to the sale price. Otherwise, sets the price variable to the customer price.
- Returns a new object that contains all of the properties of the original product object, as well as the price property that was just calculated.
- If an error occurs during the execution of the function, logs the error to the console and returns an object with an error property containing the error message.
- Exports the getProduct function as the default export of the module.
That's it. By syncing our product data, including our custom pricing, to Enterspeed, we have essentially cached it. And because we handle all the price logic server-side, only the correct price will be visible to the user.
If you want to give Enterspeed a spin, you can sign up for our free plan, which includes 5,000 source entities and 500,000 delivery API requests per month. You can check out our pricing right here.