Commercify anything: Our latest post shows how to innovate with your checkout... using Pokémon!

Tutorials

How to build a dynamic pricing engine with Commerce Layer's External Prices API and ChatGPT.

January 23, 2024 Fabrizio Picca

Dynamic pricing has been used by household names like Amazon, Uber, and Airbnb for years. Businesses shift prices in real time based on factors like demand, competitive landscape, or specific attributes of the product or service being sold. Take Airbnb’s case. Their prices are influenced by factors like seasonality; location, particularly related to other factors like an event (e.g. Paris 2024 Olympics); property size; and so on. However, the advent of AI introduces new possibilities for all brands to utilize dynamic pricing. Specifically, OpenAI's ChatGPT has revolutionized the way a business can implement a dynamic pricing strategy.

Because of the abundance of AI solutions now flooding the market across all verticals, we are often asked how a merchant might use our platform to leverage AI in their digital ecommerce experiences. To kickstart that conversation, we put together this blog post as a demo that anyone can use to integrate AI with Commerce Layer. The tutorial uses our External Prices API and leverages ChatGPT for pricing data, and can be used to implement an AI-driven dynamic pricing strategy for your business.

The goal: Implementing an AI based price engine using Chat GPT

The goal of this tutorial is to use ChatGPT to generate prices for SKUs that are added to a shopping cart. To do this, we'll set up external pricing in Commerce Layer and write some custom logic that will be deployed in the cloud using Deno Runtime. Our code will use ChatGPT to get prices based on criteria we'll specify later in this post in more detail.

The idea is pretty simple: every time a product is added to an order in Commerce Layer, the following will happen:

  1. Commerce Layer will make a POST request to our external price engine, and the payload will contain the line item that has been added to the cart.
  2. The price engine will extract the SKU's price and compare at price with the currency and send that to ChatGPT via API for evaluation based on a prompt that explains what we need.
  3. The ChatGPT API will return data in the form of a JSON object that will be sent back to Commerce Layer according to the External Prices specification.
Commerce Layer External Pricing with an AI-based pricing engine

Requirements

Before we dive into the tutorial, let's make sure we have all the necessary requirements in place:

  • A Commerce Layer Account (you can create one for free at our main website landing page commercelayer.io
  • A paid subscription to ChatGPT (you need an API key to access OpenAI APIs, and that is available only to paid subscriptions)
  • A Deno account to host our code. It's free. You can create one at https://deno.com/deploy
  • A development environment set up with Deno, OpenAI libraries, Commerce Layer SDK + CLI, and an IDE. In this tutorial, I'll be using Visual Studio Code.

Step-by-Step Tutorial

Step 1: Setting up Commerce Layer environment

If you don't have a Commerce Layer account, now is the right time to create one. You can create one for free (and it will always be free) here. To create a Commerce Layer account, you don't need a credit card. You just need to provide your first and last name, your email, and a password.

Now that you have an account, you can follow the Onboarding Tutorial that explains how to create an organization and seed it with test data. If you already have an account, you can use an existing organisation or create a new one. It's up to you.

Assuming everything goes smoothly, at the end of this step, you will have:

  • A Commerce Layer account
  • An organization with some data
  • The Commerce Layer CLI installed on your computer

Step 2: Creating the price engine

As we mentioned in the previous section, we will use Deno as a runtime to run our price engine. Deno is an alternative to Node.js that is quite handy thanks to its cloud runtime, which allows deploying TypeScript files that contain a microservices implementation. It offers a lot of useful documentation explaining how to create, deploy, and run projects.

The first step is to create a Deno account, and you can do that for free.

The Deno Dashboard

After login, you have to create a project using the empty template so we can begin implementing our price engine.

The price engine will:

  1. Receive a POST from Commerce Layer containing the line item we want to assign a price.
  2. Contact OpenAI APIs and send a prompt to instruct the model and the data.
  3. Get the result.
  4. Respond to the original POST call according to Commerce Layer's External Pricing specification.
Create an empty project on Deno.

Our example implements the service using a single TypeScript file. As you build yours, you will see a number of specific details when you're setting it up. For this blog, we don't list all of them out. But here are the key points:

  • We are using Application and Router resources from oak, a middleware built on top of the Deno native HTTP server. This manages all the I/O with Commerce Layer.
  • The communication with OpenAI is managed using the official OpenAI library for Deno. As mentioned in the requirements section, a paid subscription is needed to access the OpenAI API, so make sure to create an API key following the OpenAI instructions to get one.
import { Application, Router } from "https://deno.land/x/oak/mod.ts";
import { oakCors } from "https://deno.land/x/cors/mod.ts";
import { OpenAI } from "https://deno.land/x/openai@v4.24.0/mod.ts";

const app = new Application();
const router = new Router();
const ENDPOINT = "/api/v1/aiPriceOptimisation";

const aiClient = new OpenAI({
  apiKey: "<<YOUR API KEY HERE>>",
});

const prompt = `You are an AI based price optimisation system designed to output JSON objects. 
   I'm going to send you a JSON object with four attributes: 
   product_description (containing the description of the product I need the price for), 
   actual_price (the current price expressed in cents), 
   currency (the country I need this price for) 
   and finally compared_at_price (the previous price expressed in cents). 
   As a response I need you to suggest me a price for a product in the form of a json object 
   containing an attribute optimised_price, 
   this price should be in the range compared_at_price - actual_price and should be expressed in cents. 
   The price optimisation should be done based on the product description and the current economical context  optimising the profit.`;

async function getAIPrice(
  description: string,
  actualPrice: number,
  compareAtAmount: number,
  currency: string
): Promise<number | null> {
  const completion = await aiClient.chat.completions.create({
    model: "gpt-3.5-turbo-1106",
    response_format: { type: "json_object" },
    messages: [
      { role: "system", content: prompt },
      {
        role: "user",
        content: JSON.stringify({
          product_description: description,
          compare_at_amount: compareAtAmount,
          actual_price: actualPrice,
          currency: currency,
        }),
      },
    ],
  });

  if (completion.choices[0].message.content) {
    const response = JSON.parse(completion.choices[0].message.content);
    const optimisedPrice: number = response.optimised_price as number;

    return optimisedPrice;
  }

  return null;
}

const PORT = 3000;
router.options(ENDPOINT, oakCors());

router.post(ENDPOINT, oakCors(), async (conn) => {
  if (!conn.request.hasBody) {
    conn.throw(415);
  }
  console.log("request received");
  const body = await conn.request.body().value;
  const description: string = body.data.attributes.name;
  const currency: string = body.data.attributes.currency_code;
  const actual_price: number = body.data.attributes.unit_amount_cents as number;
  const compare_at_amount_cents: number = body.data.attributes
    .compare_at_amount_cents as number;

  const optimisedPrice = await getAIPrice(
    description,
    compare_at_amount_cents,
    actual_price,
    currency
  );
  conn.response.body = {
    success: true,
    data: {
      sku_code: body.data.attributes.sku_code,
      unit_amount_cents: optimisedPrice
        ? optimisedPrice
        : body.data.attributes.unit_amount_cents,
    },
  };
});

app.use(oakCors());
app.use(router.routes());
app.use(router.allowedMethods());

console.log(`Server running on port ${PORT}`);
await app.listen({ port: PORT });

Let’s analyze the main parts of the code to understand how it works.

The request handler

The oak middleware offers a very easy way to manage incoming POST requests. Each of them will be managed with the following code:

router.post(ENDPOINT, oakCors(), async (conn) => {
  if (!conn.request.hasBody) {
    conn.throw(415);
  }
  console.log("request received");
  const body = await conn.request.body().value;
  const description: string = body.data.attributes.name;
  const currency: string = body.data.attributes.currency_code;
  const actual_price: number = body.data.attributes.unit_amount_cents as number;
  const compare_at_amount_cents: number = body.data.attributes
    .compare_at_amount_cents as number;

  const optimisedPrice = await getAIPrice(
    description,
    compare_at_amount_cents,
    actual_price,
    currency
  );
  conn.response.body = {
    success: true,
    data: {
      sku_code: body.data.attributes.sku_code,
      unit_amount_cents: optimisedPrice
        ? optimisedPrice
        : body.data.attributes.unit_amount_cents,
    },
  };
});

For each line item created, Commerce Layer will send a POST request containing a payload of the line item data. The handler extracts from the payload:

  • a product description
  • the currency used in the transaction
  • the actual price
  • the "compare at" price

This data will be sent to OpenAI API by means of the getAIPrice helper function, wrapping OpenAI API calls.

Getting data from OpenAI API

The price engine makes use of OpenAI ChatGPT to dynamically assign a price to a line item. To do so we need to instruct ChatGPT what is required. First, create a prompt that states the scenario and initialise the connection with ChatGPT. Let’s take a look at the prompt:

const prompt = `You are an AI based price optimisation system designed to output JSON objects. 
   I'm going to send you a JSON object with four attributes: 
   product_description (containing the description of the product I need the price for), 
   actual_price (the current price expressed in cents), 
   currency (the country I need this price for) 
   and finally compared_at_price (the previous price expressed in cents). 
   As a response I need you to suggest a price for a product in the form of a json object 
   containing an attribute optimised_price, 
   this price should be in the range compared_at_price - actual_price and should be expressed in cents. 
   The price optimization should be based on both the product description and any relevant economic factors in order to optimize the profit.`;

Yeah, you are reading that correctly. You need to tell ChatGPT what you want it to do just as you would in a usual chat session. The only difference here is that you store the prompt in a variable, and then send it to initialise the chat session. The request is pretty simple: When we send a product description, a price range, and a currency, return a price that optimizes profit and considers any other economic factors.

ℹ️ Keep in mind that this is just a PoC. The data returned by this price engine is not accurate. You will want to use a proprietary AI engine that is trained with historical pricing data. Our example shows how tools might be used in a real world scenario.

Now, let’s see how this is implemented.

async function getAIPrice(
  description: string,
  actualPrice: number,
  compareAtAmount: number,
  currency: string
): Promise<number | null> {
  const completion = await aiClient.chat.completions.create({
    model: "gpt-3.5-turbo-1106",
    response_format: { type: "json_object" },
    messages: [
      { role: "system", content: prompt },
      {
        role: "user",
        content: JSON.stringify({
          product_description: description,
          compare_at_amount: compareAtAmount,
          actual_price: actualPrice,
          currency: currency,
        }),
      },
    ],
  });

  if (completion.choices[0].message.content) {
    const response = JSON.parse(completion.choices[0].message.content);
    const optimisedPrice: number = response.optimised_price as number;

    return optimisedPrice;
  }

  return null;
}

The task is completed by the aiClient.chat.completions.create method that takes the prompt, inputs data as parameters, and returns a result.

As you can see, we are telling the OpenAI API which model we want to use, in our case, gpt-3.5-turbo-1106. We want this specific version because it allows us to receive a JSON object in response. You can see this by looking at the response_format: { type: "json_object" } property we are passing).

Pretty straightforward, isn’t it?

Deploying our function in the cloud.

Now that we have the code, we just need to deploy it in the cloud. Deno will help us with its cloud runtime:

deployctl deploy --project=posh-bobcat-82 service.ts

The deployctl command will input both the project at the beginning of this section and the TypeScript file we created. Please refer to Deno documentation for deployment instructions.

The result is the following:

C11RFPMAC:ai-price-generator % deployctl deploy --project=posh-bobcat-82 service.ts
ℹ Using config file '/Users/fabriziopicca/Sites/ai-price-generator/deno.json'
✔ Deploying to project posh-bobcat-82.
✔ Entrypoint: /Users/fabriziopicca/Sites/ai-price-generator/service.ts
ℹ Uploading all files from the current dir (/Users/fabriziopicca/Sites/ai-price-generator)
✔ Found 4 assets.
✔ Uploaded 1 new asset.
✔ Preview deployment complete.

View at:
 - https://posh-bobcat-82-kz1amga83abq.deno.dev

Our service is now deployed in the cloud at the address https://posh-bobcat-82-kz1amga83abq.deno.dev.

ℹ️ Deno will add to the base url a deployment identifier (kz1amga83abq). This allows having multiple versions deployed of the same service. If the deployment identifier is not provided (https://posh-bobcat-82.deno.dev ), Deno will point to the last deployment.

Now we need to configure External Pricing in Commerce Layer so that it will point to the new endpoint:

https://posh-bobcat-82-kz1amga83abq.deno.dev/api/v1/aiPriceOptimisation

Note the full endpoint name. that results from the setup we did in the service code:

const app = new Application();
const router = new Router();
const ENDPOINT = "/api/v1/aiPriceOptimisation";
...
const PORT = 3000;
router.options(ENDPOINT, oakCors());
...
app.use(oakCors());
app.use(router.routes());
app.use(router.allowedMethods());

console.log(`Server running on port ${PORT}`);
await app.listen({ port: PORT });

Step 3: Setting up Commerce Layer

Now that we implemented our AI-powered price engine, it’s time to integrate it with Commerce Layer. Commerce Layer allows prices to be fetched from an external systems using (External Pricing. It’s pretty simple. When a line item is created using the _external_price flag set to true, Commerce Layer will contact the endpoint set in the market configuration to get the price for the specific line item. In our case we are going to use our AI-powered price engine as the external price resource. Here’s what the configuration looks like:

In the organization that we set up in the Commerce Layer Dashboard, go to Settings → Markets.

Commerce Layer Settings where you can find Markets

In the Market configuration (USA in this case), add the endpoint created in the previous step:

Adding an External Prices URL to your Markets configuration

Hit save and the Commerce Layer configuration is done. That’s it! From this point on, Commerce Layer will delegate price definition to our endpoint if the _external_price=true is added when creating a line item.

Step 4: Putting everything together

It’s time to test the set up and see the final result. Open a terminal and fire up Commerce Layer CLI. In the onboarding phase, you should have created an application for the CLI. It’s time to use it! Using the CLI with a Sales Channel access token, first create an empty order:

C11RFPMAC:~ % cl create order
...
Successfully created new resource of type orders with id gojhLmzVkM

Now, add a line_item to our order asking CL to use the external price.

C11RFPMAC:~ % cl create line_item -r order=gojhLmzVkM -a quantity=1 _external_price=true -r item=skus/ZALKSwrbNy
...
Successfully created new resource of type lineitems with id mnptRrrMzq

This is where we hit the AI-powered price engine and get an optimized price. If we want to see the resulting price, open a checkout page for the newly created order:

C11RFPMAC:~ % cl checkout -O gojhLmzVkM --open

Commerce Layer Hosted Checkout will be displayed showing the optimized price of $105 for our SKU…that has a standard price of $96!

Commerce Layer's checkout page with an AI-powered price. In this example, it's powered by OpenAI's ChatGPT.

Conclusion

You might be asking if this is Commerce Layer's "AI-solution" amid all the AI buzz? First, we’ll refer you to a post written by Commerce Layer’s CEO and Cofounder, Filippo Conforti. He wrote a blog titled AI Demystified. He wrote the post based on his expertise from his master’s degree in computer science that focused on artificial intelligence. In his post, he largely covers two things:

  1. How AI works, and
  2. How it might be applied in ecommerce.

Filippo offers up this provocation toward the end of the post:

Ecommerce can greatly benefit from similarity search and generative AI, based on the same vector and machine learning principles described above. You can use AI to generate product descriptions, automate search, make recommendations, personalise shopping experiences, or engage in conversational commerce.

All these use cases have one thing in common: they all manipulate content. While AI is capable of giving users immersive and innovative ways to discover products, it might not have the same impact when it comes to showing a price, the availability of a product, or managing a shopping cart.

I believe AI-generated prices and promotions are already being used in some industries (such as the airline industry), but AI cannot generate stock that doesn't exist in a warehouse or manipulate credit card transactions. You might be able to use AI to optimise the risk assessment of a transaction, but not the transaction itself.

You should read the entire post to get a firm understanding of how Filippo reaches this conclusion. And, if you’re not following Filippo’s blog, Commerce for Devs, give it a try. It’s a great source for anyone in the ecommerce field, even non-devs.

In closing, Commerce Layer’s External Prices API combined with a price optimization AI (OpenAI's ChatGPT is probably not your best choice) opens up a world of possibilities right now for businesses to get the most out of their pricing strategies. By leveraging the power of our unopinionated, yet extensible API, and artificial intelligence, any merchant can take advantage of the tools that exist in the market to offer data-driven pricing that adapts to changing market conditions and customer behavior. One caveat: “Your mileage may vary”. We recommend that you carefully monitor whatever you put in place to ensure effectiveness and avoid any unintended consequences (our Metrics API can help you with that). With this approach, dynamic pricing with an AI-service focused on price optmizations can help you maximise profitability.