Skip to main content
Skip to main content

How to Override the Price Selection Strategy

In this document, you’ll learn what the price selection strategy and how to override it in the Medusa backend.

Overview

The price selection strategy retrieves the best price for a product variant for a specific context such as selected region, taxes applied, the quantity in cart, and more.

Medusa provides a default price selection strategy, but you can override it. A price selecion strategy is a TypeScript or JavaScript file in the src/strategies directory of your Medusa backend project. It exports a class that extends the AbstractPriceSelectionStrategy class.

For example:

src/strategies/price.ts
import {
AbstractPriceSelectionStrategy,
PriceSelectionContext,
PriceSelectionResult,
} from "@medusajs/medusa"

export default class MyStrategy extends
AbstractPriceSelectionStrategy {

async calculateVariantPrice(
data: {
variantId: string;
quantity?: number
}[],
context: PriceSelectionContext
): Promise<Map<string, PriceSelectionResult>> {
throw new Error("Method not implemented.")
}
}

constructor

You can use the constructor of your price-selection strategy to access the different services in Medusa through dependency injection.

Example

// ...
import {
AbstractPriceSelectionStrategy,
CustomerService,
} from "@medusajs/medusa"
type InjectedDependencies = {
customerService: CustomerService
}

class MyStrategy extends
AbstractPriceSelectionStrategy {

protected customerService_: CustomerService

constructor(container: InjectedDependencies) {
super(container)
this.customerService_ = container.customerService
}

// ...
}

export default MyStrategy

Parameters

containerRecord<string, unknown>Required
An instance of MedusaContainer that allows you to access other resources, such as services, in your Medusa backend.
configRecord<string, unknown>
If this price-selection strategy is created in a plugin, the plugin's options are passed in this parameter.

Methods

calculateVariantPrice

This method retrieves one or more product variants' prices. It's used when retrieving product variants or their associated line items. It's also used when retrieving other entities that product variants and line items belong to, such as products and carts respectively.

Example

For example, here's a snippet of how the price selection strategy is implemented in the Medusa backend:

import {
AbstractPriceSelectionStrategy,
CustomerService,
PriceSelectionContext,
PriceSelectionResult,
} from "@medusajs/medusa"

type InjectedDependencies = {
customerService: CustomerService
}

export default class MyStrategy extends
AbstractPriceSelectionStrategy {

async calculateVariantPrice(
data: {
variantId: string
quantity?: number
}[],
context: PriceSelectionContext
): Promise<Map<string, PriceSelectionResult>> {
const dataMap = new Map(data.map((d) => [d.variantId, d]))

const cacheKeysMap = new Map(
data.map(({ variantId, quantity }) => [
variantId,
this.getCacheKey(variantId, { ...context, quantity }),
])
)

const nonCachedData: {
variantId: string
quantity?: number
}[] = []

const variantPricesMap = new Map<string, PriceSelectionResult>()

if (!context.ignore_cache) {
const cacheHits = await promiseAll(
[...cacheKeysMap].map(async ([, cacheKey]) => {
return await this.cacheService_.get<PriceSelectionResult>(cacheKey)
})
)

if (!cacheHits.length) {
nonCachedData.push(...dataMap.values())
}

for (const [index, cacheHit] of cacheHits.entries()) {
const variantId = data[index].variantId
if (cacheHit) {
variantPricesMap.set(variantId, cacheHit)
continue
}

nonCachedData.push(dataMap.get(variantId)!)
}
} else {
nonCachedData.push(...dataMap.values())
}

let results: Map<string, PriceSelectionResult> = new Map()

if (
this.featureFlagRouter_.isFeatureEnabled(
TaxInclusivePricingFeatureFlag.key
)
) {
results = await this.calculateVariantPrice_new(nonCachedData, context)
} else {
results = await this.calculateVariantPrice_old(nonCachedData, context)
}

await promiseAll(
[...results].map(async ([variantId, prices]) => {
variantPricesMap.set(variantId, prices)
if (!context.ignore_cache) {
await this.cacheService_.set(cacheKeysMap.get(variantId)!, prices)
}
})
)

return variantPricesMap
}

// ...
}

Parameters

dataobject[]Required
The necessary data to perform the price selection for each variant ID.
contextPriceSelectionContextRequired
The context of the price selection.

Returns

PromisePromise<Map<string, PriceSelectionResult>>Required
A map, each key is an ID of a variant, and its value is an object holding the price selection result.

onVariantsPricesUpdate

This method is called when prices of product variants have changed. You can use it to invalidate prices stored in the cache.

Example

For example, this is how this method is implemented in the Medusa backend's default price selection strategy:

import {
AbstractPriceSelectionStrategy,
CustomerService,
} from "@medusajs/medusa"
import { promiseAll } from "@medusajs/utils"

type InjectedDependencies = {
customerService: CustomerService
}

export default class MyStrategy extends
AbstractPriceSelectionStrategy {

public async onVariantsPricesUpdate(variantIds: string[]): Promise<void> {
await promiseAll(
variantIds.map(
async (id: string) => await this.cacheService_.invalidate(`ps:${id}:*`)
)
)
}

// ...
}
Note

Learn more about the cache service in this documentation.

Parameters

variantIdsstring[]Required
The IDs of the updated variants.

Returns

PromisePromise<void>Required
Resolves after any necessary actions are performed.

Test Implementation

Note

If you created your price selection strategy in a plugin, refer to this guide on how to test plugins.

After finishing your price selection strategy implementation:

1. Run the build command in the root of your Medusa backend:

npm run build

2. Start the backend with the develop command:

npx medusa develop

3. To test out your price selection strategy implementation, you can retrieve a product and it's variants by specifying pricing parameters as explained in this guide.

Was this section helpful?