Skip to main content

Introduction to prices

There's more to prices than meets the eye. If you want to know more about prices in EVA in general, see Pricing as a concept. If you're looking for a guide on how to create price lists (or adjustments) by means of the UI in Admin Suite, see Prices in Admin Suite. The remainder of this page will focus on creating them via the API.

note

When adding a new price list to organization unit or a price list adjustment of usage type selling or sale price, you can attach an alternative price list that would only be applicable to a specific group of customers by making use of the PricingGroupID property.

This property is available on all CRUD services for PriceListAdjustment, and PriceListOrganizationUnit.

Ways to create and update prices

As said before, there's much you can do with prices via our UI in Admin Suite. There's more than one way to do so via services too however. There's a service for each specific step in the process of creating a price list (adjustment), while we've also got a single, all-encompassing service for it called PushPriceList. Obviously, we'd recommend the latter, but the more specific ones help paint a clear picture of setting pricelists and the like.

We'll start of with the specific ones, but feel free to scroll down to PushPriceList

Creating a pricelist

First off, we need to create the actual pricelist. To do so, we call CreatePriceList. Alternatively, you can create the pricelist via Admin Suite, and only use your API interface for updating the prices.

{
"Name": "Sales NL",
"CurrencyID": "EUR",
"TimeZone": "Europe/Amsterdam",
"IncludingVat": true,
"IsActive": true
}

A pricelist needs a name, currency and a time zone. The time zone helps with calculating the eventual effective and expiry dates. Additionally, we have to specify whether or not the prices will include VAT and whether or not the pricelist should be active.

note

There is no harm in setting the pricelist to active since it's not attached to an OU yet.

Adjustment layer

After creating the pricelist, we can start adding adjustment layers. To add an adjustment layer, call CreatePriceListAdjustment using the ID we had returned from CreatePriceList.

{
"PriceListID": 1,
"Name": "Fall/Winter 2021",
"EffectiveDate": "2021-06-31T00:00:00Z",
"ExpireDate": "2021-12-31T00:00:00Z",
"Type": "MANUAL_INPUT"
}

Make sure to set the Type to MANUAL_INPUT to allow us to manually input the prices later. The service returns the ID for the newly created pricelist.

Adding prices

Now we can start adding prices to the created adjustment layer, for which we recommend UpdateSimplePrices. As a reminder, you could also use PushPriceList which can add it directly to an OU as well (which is the step due in the next section).

{
"PriceListID": 1,
"Prices": [
{
"ProductID": 34,
"NewPrice": 16.99
}
]
}

Attaching pricelists to an OU

After completing your pricelist, you can attach it to an organization unit using CreatePriceListOrganizationUnit. This is also where we specify the pricelist's usage type. Possible usage types:

IDUsageTypeFront-end name
1Purchase priceUsed for sales price between B2B
2Cost priceCost of products as calculated for the seller itself
3Selling pricePrice for normal B2C sales scenarios
4Sale priceThis is the price when a product is on sale (not to be confused with discounted). The sale price specified here will override the selling price during the sale period.

It can work in combination with the historical price.
5Recommended retail priceDisplays a recommended retail price on the original selling price. (For display purposes only)
6Potential pricesPotential prices displayed in strikethrough, as a discounted from price. (For display purposes only)
7EcoTaxUsed to calculate eco taxes on products.
8HistoricalThis price list is different from the others, since you cannot attach prices yourself. Instead, this shadow price list will track the products' lowest prices and will display them when a product is on sale.

WIP: please see Historic prices.

Example request:

{
"OrganizationUnitID": 4,
"TargetOrganizationUnitID": 3, // optional
"PriceListID": 1,
"PriceListUsageTypeID": 3
}

note

It is possible to adjust pricelists after having already linked it to an organization unit.

Alternative prices

Alternative prices for different organization units

To make it possible for an (supplying) organization unit to have different prices for different (buying) OUs, you can add more than one price list to the (supplier) OU. To specify which buying OU should get what prices, you can set the property TargetOrganizationUnitID on a price list. This way the right price list will be used for purchase orders from that 'Target' OU. To accommodate this, the target OU should have a Cost price list configured to be populated with Supplier prices.

For now, this property can only be specified with the services mentioned here, such as UpdatePriceListOrganizationUnit.

Alternative prices for different customers

By making use of the PricingGroupID property, you can attach an alternative price list that would only be applicable to a specific group of users.

This property is available on all CRUD services for User, PriceListAdjustment, and PriceListOrganizationUnit. Meaning that you can use this property when adding a new price list to an organization unit or a price list adjustment of usage type selling or sale price.

A PricingGroupID will determine which price list will apply to a user. After specifying a PricingGroupID for your user, you now need to make a link with an organization unit and its corresponding price list using that same PricingGroupID. This can be done using PriceListOrganizationUnit.

Example: if the user has a PricingGroupID 'A', and there is a price list for that organization unit with that same PricingGroupID 'A', then that price list will be applied to that user. Goes without saying, that if there is no match in the PricingGroupID between the two, then the normal price list tagged to that organization unit will apply.

PricingGroupID requirement

The CreateCustomer service used to set the PricingGroupID can only be used by Employee or API type users.

Pricing groups based on custom fields

Instead of using the PricingGroupID specifically, you can opt to base a user's PricingGroupID on the value of custom fields instead. You can do so by filling the value of the Pricing:PricingGroupFromCustomField setting with a JSON object containing the custom field.

Details and examples
{
"CustomFields" = [{
"Name" = "my_custom_field", // Name of the custom field
// If specified, this will be the *PricingGroupID* for the user. If not specified, the user's *PricingGroupID* will be the value of the custom field.
// So if the user has a custom field called "my_custom_field" with a value of *1*, then the user's PricingGroupID will be *1*.
"PricingGroupID" = "my_pricing_group_id",
// However if *PricingGroupID* is specified, then the user's *PricingGroupID* will be the value of *PricingGroupID*.
"ConditionValue" = "value_of_the_custom_field"
// If specified then the custom field on the user must have this value before the user's *PricingGroupID* will be changed.
}]
}

The following basic example shows you how a customer with a custom field called customer_group, will have its PricingGroupID set to whatever the value of the customer_group is: in this case VIP.

{
"CustomFields" : [{
"Name" : "customer_group",
"PricingGroupID" : "vip"
}]
}

This can also be done based on conditions, as the following example shows you. If the customer_group custom field's value is vip_1, then the PricingGroupID will be set to vip. While if the value is loy, then the ID will be set to loyal.

This can also be done based on conditions, as the following example shows you. If the customer_group custom field's value is vip_1, then the PricingGroupID will be set to vip. While if the value is loy, then the ID will be set to loyal.

{
"CustomFields" : [{
"Name" : "customer_group",
"PricingGroupID" : "vip",
"ConditionValue" : "vip_1"
},{
"Name" : "customer_group",
"PricingGroupID" : "loyal",
"ConditionValue" : "loy"
}]
}

Do note that this setting does not work retroactively: only when a user's custom fields change, or when a customer is attached to an order, will the setting be applied.

Displaying potential prices

By using the service GetPotentialProductPricing, you can check any potential prices aside from the current price. This may be beneficial when you have multiple loyalty ranks allowing for bigger discounts. You can also use this to display the discount a customer is already getting due to the loyalty discount.

Moreover, the potential price also displays the condition necessary for getting that price, which is currently only possible with a custom field configuration (like in the chapter here above).

Details and examples

The following examples show you the results of GetPotentialProductPricing based on 'Example 2' mentioned here above.

{
"UserID": 99,
"ProductIDs": [
149
]
}

Eco taxes

To manage eco taxes on products, you can create a standard price list using any currently supported method with all currently supported features, like markup, product search templates and date ranges. This price list must then be linked to an organization unit with a price list usage type called EcoTax.

This price list (read: usage type) is used to calculate the eco taxes, separate from any product price and is visible through services like SearchProducts and GetProductDetails. For now we assume this eco tax is already part of the product unit price that is determined through the regular price lists (although this might be expanded to define other setups at a later stage).

When adding a product to your basket, we store the current applicable eco tax as order line information. Because we stated the unit price already includes this eco tax, we explicitly do not incorporate this additional information in further order calculations, like regular taxes or discounts. Applying a 100% discount on such products will yield a free order line, while leaving the stored eco tax information unchanged.

We will return this additional information through GetOrder and similar checkout services.

Corresponding properties such as EcoTaxinTax and TotalEcoTaxinTax are only exposed when setting Orders:Display:ShowEcoTax is set to true (defaults to false). This setting is also exposed as an app setting, allowing the app to make rendering decisions for PDP or CDP or similar.

PushPriceList

Price list usage types

This service uses PriceListUsageTypes enum (see below), and is therefore, different from the GetPriceListUsageTypes.

This is where we bring in the big guns. By using PushPriceList, you can combine all the above steps and create or update all of these aspects in a single take.

To make it even easier from an integration perspective, this service works with BackendIDs by default. Some properties have been renamed in comparison to the services here above, to make them clearer:

  • Adjustments are instead called Components: a PriceList is made up out of components
  • The component type Manual input is called PriceEntries
  • Component type PriceList is called CopyPricesFromOtherPriceList
  • IncludingVat is called SpecifiedPricesIncludeTax

For sake of clarity, we'll go through all properties you can use in this service.

PushPriceList properties

*/
export interface PushPriceList {
ID: string;
/**
* The ID property makes for the unique identifier for this pricelist.
*/
SystemID: string;
/**
* A string that identifies the system that was used to call this service. In combination with the ID of the pricelist, this is what uniquely identifies the pricelist in EVA.
* The SystemID must be the same for every call, if it's different from a previous call then a new pricelist will be created.
*/
SpecifiedPricesIncludeTax?: boolean;
/**
* If SpecifiedPricesIncludeTax is set to true, then all prices specified on this price list will be interpreted as already VAT-included. If set to false, all prices will be interpreted as VAT-excluded. If not specified then the default value is true.
*
* So if a price entry of 90 EUR is added to this pricelist with SpecifiedPricesIncludeTax = true (or not specified) then the price will be interpreted as 90 EUR already including VAT.
* If SpecifiedPricesIncludeTax = false then the price will be interpreted as 90 EUR excluding VAT so that will be added on top for any orders created with this price.
*/
Name?: string;
/**
* The Name of the pricelist. Must be present for the creation of a pricelist but can be left empty in subsequent calls.
*/
IsActive?: boolean;
/**
* Whether or not the pricelist is active.
*/
TimeZone?: string;
/**
* The timezone that will be used to interpret activation times for prices.
*/
CurrencyID?: string;
/**
* The currency for the pricelist. Must be present for the creation of a pricelist but can be left empty in subsequent calls.
*/
Components?: Component[];
/**
* The list of components that make up the pricelist. If left empty, this will leave the current configuration unchanged. If specified, only the components present will be modified. Components must be explictly deleted.
*/
ActiveForOrganizationUnits?: ActiveOrganizationUnit[];
/**
* The organization units that use this pricelist. If left empty, it will leave the current configuration unchanged. If specified, will replace the current configuration.
*/
}

export interface Component {
ID: string;
/**
* The unique identifier for this component. Required.
*/
Name?: string;
/**
* The *Name* of the component. If left empty on creation of the component, the *ID* will be used as the name.
*/
Type: ComponentTypes;
/**
* Determines the *Type* of the component. Cannot be changed after creation.
*/
StartDate?: string;
/**
* Determines when this component will become active. Until then the component will not be included in the calculation of the pricelist.
*/
EndDate?: string;
/**
* Determines when this component will become inactive. After this date the component will not be included in the calculation of the pricelist.
*/
PricingGroupID?: string;
/**
* The *PricingGroupID* that this component is active for. Components with a *PricingGroupID* are only active for users that are members of that group. If left empty, the component will be active for all users.
*/
SequenceNumber?: number;
/**
* Components are applied in the order of their sequence number, from lowest to highest.
*/
Delete: boolean;
/**
* If specified as true, the component will be deleted.
*/
PriceEntriesData?: PriceEntriesData;
/**
* The data that accompanies a Component with Type = PriceEntries, so a component that is simply a list of product/price pairs.

* The simplest and most direct way to give a product a price.
*
* This data consists of either a list of product/price pairs or a BlobID which is a reference to a previously created blob in EVA that contains data of the following format:
*
* [
* {
* "ProductID": "<ProductID>",
* "Price": <Price
* },
* ...
* ]
*
* If you only supply a handful of prices, the inline data works well, but for large quantities of prices you may want to use the BlobID method.
*
* This data is ignored for all other Component types.
*
* PriceEntries data is processed asynchronously in the background and will not be immediately visible after the service call returns.
*/
CopyPricesFromOtherPriceListData?: CopyPricesFromOtherPriceListData;
/**
* The data that accompanies a Component with Type = PricesFromOtherPriceList which is a component to copy prices from another pricelist.
*
* Is useful for example for promotion prices, where you first 'import' the prices from the original pricelist, and then apply a negative markup to them to create a promotion discount.
*
* Which prices you copy can be modified by specifying a ProductSearchTemplateID, only products that are contained in the results of the search will be copied,
* unless ExcludeProductsInFilter is set to true, in which case the reverse is true; all products except the matching ones will have their prices copied.
*/
MarkupData?: MarkupData;
/**
* The data that accompanies a Component with Type = Markup which is a component that alters the price value of the previusly applied components,
* either up or down. The markup/markdown can either be an absolute value or a percentage depending on the Type.
*/
}
export const enum ComponentTypes {
PriceEntries = 0,
Markup = 1,
CopyPricesFromOtherPriceList = 2
}

export interface PriceEntriesData {

BlobID?: string;
/**
* The blob that refers to the price entry data that was previously uploaded to a Blob in EVA. If left empty the inline data will be used.
*
* The data in the blob must be in the following format:
*
* [
* {
* "ProductID": "<ProductID>",
* "Price": <Price>
* },
* ...
* ]
*/
Prices?: PriceEntryPrice[];
/**
* The inline data that will be used to create the price entries. If left empty the blob will be used.
*/
}

export interface PriceEntryPrice {
ProductID: string;
Price?: number;
}

export interface CopyPricesFromOtherPriceListData {
PriceListID: string;
/**
* The ID of the pricelist to copy prices from.
*/
ProductSearchTemplateID?: number;
/**
* The ID of the ProductSearchTemplate to use to filter the products to copy. If left empty all products will be copied.
* Entity type: ProductSearchTemplate
*/
ExcludeProductsInFilter: boolean;
/**
* If true, all products except the matching ones will have their prices copied. If false, only the matching ones will have their prices copied.
*/
}

export interface MarkupData {
Type: FactorType;
/**
* The type of markup/markdown. Either absolute amount or a percentage. Determines how FactorValue will be interpreted.
*/
FactorValue: number;
/**
* The value of the markup/markdown. If Type is Amount, this is the amount to add to the price.
* If Type is Percentage, this is the amount that the Price will be multiplied by.
*
* A 10% markup should be specified as 1.10, a 25% discount should be specified as 0.75.
*/
ProductSearchTemplateID?: number;
/**
* The ID of the ProductSearchTemplate to use to filter the products to copy. If left empty all products will be copied.
* Entity type: ProductSearchTemplate
*/
ExcludeProductsInFilter: boolean;
/**
* If true, all products except the matching ones will have their prices copied. If false, only the matching ones will have their prices copied.
*/
}

export const enum FactorType {
Amount = 0,
Percentage = 1
}

export interface ActiveOrganizationUnit {
OrganizationUnitID: string;
UsageType: PriceListUsageTypes;
TargetOrganizationUnitID?: string;
PricingGroupID?: string;
Delete: boolean;
}

export const enum PriceListUsageTypes {
Sales = 0,
Cost = 1,
Promotion = 2,
Purchase = 3,
RecommendedRetail = 4
}


}

Take into account that you can display and stack multiple prices of the same product by means of the BackendID (which translates to ID in any Push-service). Each price will come with its own start and end dates, making for easy price management.

Example of stacking prices
BackendIDStart dateEnd datePrice
1231-Jan-2331-Dec-23100
8901-Feb-233-Feb-23103
4561-Jan-2431-Dec-2490

This would translate to the following situation:

  • On the 1st of Jan 2023 at 0 hundred hours it would cost 100
  • On the 1st of Feb 2023 at 0 hundred hours it would cost 103
  • On the 4th Feb 2023 at 0 hundred hours the cost would revert back to 100
  • On the 1st of Jan 2024 it would cost 90


"Components": [
{
"Name": "Test adjustment",
"ID": "MSRP",
"Type": 0,
"StartDate": "2023-01-02T00:00:00-07:00",
"Delete": false,
"PriceEntriesData": {
"Prices": [
{
"ProductID": "015-1-978020137058",
"ID": "BE1_1",
"Price": 11
"StartDate": "2023-01-02T00:00:00-07:00",
"EndDate": "2023-12-31T00:00:00-07:00",
},
{
"ProductID": "015-1-978020137058",
"ID": "BE1_2",
"Price": 12
"StartDate": "2024-01-01T00:00:00-07:00",
"EndDate": null
},

We've got three practical samples to show how you can use the service.

PushPriceList example cases
{
"ID": "u5lj84ef10",
"SystemID": "TEST_SUITE",
"SpecifiedPricesIncludeTax": "undefined",
"Name": "248lvcuu2b",
"IsActive": "undefined",
"TimeZone": "undefined",
"CurrencyID": "EUR",
"Components": [
{
"ID": "obo5ll4h28",
"Name": "d9m2cfljbz",
"Type": "PriceEntries",
"StartDate": "2022-07-18T00:00:00-07:00",
"EndDate": "2023-05-14T00:00:00-07:00",
"PricingGroupID": "ptluplakva",
"SequenceNumber": "undefined",
"Delete": "false"
}
]
}

Failed product updates

Some of the products contained in the price list push might not be resolved correctly. You can check which (if any) products failed by means of the UnknownProducts property.

Do take note that, while we are working on it, for the time being this property will only be populated if the push contains < 100 products.

PushPriceListAsync

While PushPriceList is limited to 100 products when you want its results to include unresolved products, PushPriceList_Async has no such limits.

It will allow you to process price lists in an async manner and return an async result ID. This can then be used in the PushPriceList_AsyncResult service to give you the PriceListID and display any unresolved products.

Sample
{
"PriceListID": 75,
"UnknownProducts": [
"935353000011",
"9035300013",
"3535",
"DTEASLFLSA",
"XXXSDFSF"
],
"Metadata": {
"ExternalIDs": {},
"IsAsyncResultAvailable": true
}
}