<?xml version="1.0" encoding="utf-8"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><language>en</language><title>Blog posts by Shannon Gray</title> <link>https://world.optimizely.com/blogs/Shannon-Gray/</link><description></description><ttl>60</ttl><generator>Optimizely World</generator><item> <title>Episerver Catalog Packages vs Bundles</title>            <link>https://world.optimizely.com/blogs/Shannon-Gray/Dates/2017/6/episerver-catalog-packages-vs-bundles/</link>            <description>&lt;p&gt;This is a short blog to clarify a catalog concept that is often unclear for folks new to Episerver : the difference between bundles and packages. &lt;/p&gt; &lt;p&gt;Bundles and packages are catalog item-types that have distinct features and different use cases. &lt;/p&gt; &lt;p&gt;A &lt;strong&gt;bundle&lt;/strong&gt; is a collection of SKUs that are simply grouped together for catalog-browse and add-to-cart purposes. There is no bundle-specific pricing or inventory. The pricing for a bundle is simply the total of the prices for the SKUs in the bundle. Inventory is tracked on the SKU level. A common scenario for using bundles is when an item usually needs to be bought in conjunction with other items in the catalog, like a phone and its charger. &lt;/p&gt; &lt;p&gt;When a bundle is added to the cart, your “add to cart” functionality needs to retrieve the SKUs in the bundle from the system and add each one to the cart. Once in the cart, the SKUs from the bundle are not identifiable as part of a bundle; they’re simple SKUs (as lineitems) in the cart. Therefore, a) you can’t attribute promotions to them as a bundle, b) you can change the quantity of each SKU/lineitem independently, and c) you can delete the individual lineitems that were associated with the bundle SKUs.&amp;nbsp; Given the lack of pricing on a bundle and that promotions can’t be applied to them, you can’t provide discount pricing to incentivize buying bundles (at least not without significant additional development).&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;/link/1d3c669ffc024e8b960dcca906a31106.aspx&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;/link/debbce62d3e74eb38352d0f19438f9a7.aspx&quot; width=&quot;644&quot; height=&quot;208&quot; /&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;p&gt;Conversely, &lt;strong&gt;packages&lt;/strong&gt; contain SKUs but have their own pricing and inventory. When you add a package to the cart, its a single lineitem, like a SKU. SKUs can’t be removed from the package lineitem in the cart. You can only update the quantity of the package in the cart (or remove it). You can think of a package as a group of SKUs that are physically stored together and inventory is managed on the package level.&amp;nbsp; Packages allow you to provide discounted pricing for a group of SKUs. You can also create promotions for packages. Note, however, that promotions which apply to SKUs contained in the package will not apply to the package lineitem.&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;/link/562a4e9637c240989e41917d3e1c4251.aspx&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;/link/8c7566026612436486b73207ef9b57dc.aspx&quot; width=&quot;644&quot; height=&quot;207&quot; /&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;p&gt;These catalog types and their behavior are flexible with some minor customization. You could do things like:&lt;/p&gt; &lt;p&gt;* Create a “dynamic package”, where a user can select which items in a package or bundle is ultimately purchased in their cart.&lt;/p&gt; &lt;p&gt;* Use packages but still maintain inventory on the SKU level&lt;/p&gt; &lt;p&gt;* Add a package as multiple lineitems in the cart&lt;/p&gt; &lt;p&gt;These customizations may require additional effort to a) ensure your pricing logic reflects this logic, b) modify the way carts are processed to validate the content of the carts, update prices, etc , and/or c) change the way your add to cart functionality works.&lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;p&gt;Documentation on how to work with packages and bundles programmatically is here:&lt;/p&gt; &lt;p&gt;&lt;a title=&quot;http://world.episerver.com/documentation/developer-guides/commerce/catalogs/catalog-content/Bundles-and-packages/&quot; href=&quot;/link/5145a926f6094d0aa3da3093baeab489.aspx&quot;&gt;http://world.episerver.com/documentation/developer-guides/commerce/catalogs/catalog-content/Bundles-and-packages/&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;em&gt;[Edited to incorporate comments below from Khurram and Per from 6/9/17]&lt;/em&gt;&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/Shannon-Gray/Dates/2017/6/episerver-catalog-packages-vs-bundles/</guid>            <pubDate>Fri, 09 Jun 2017 03:41:58 GMT</pubDate>           <category>Blog post</category></item><item> <title>Episerver Releases B2B Demo Site</title>            <link>https://world.optimizely.com/blogs/Shannon-Gray/Dates/2017/4/episerver-releases-b2b-demo-site/</link>            <description>&lt;p&gt;Episerver just released the source code for a B2B demo site. This site is designed to showcase how Episerver easily supports complex B2B capabilities. Ed Kennedy and Jacob Khan gives a great introduction to the site below. &lt;br /&gt;&lt;br /&gt; &lt;div class=&quot;wistia_responsive_padding&quot; style=&quot;position: relative; padding-bottom: 0px; padding-top: 400px; padding-left: 0px; clear: both; padding-right: 0px&quot;&gt; &lt;div class=&quot;wistia_responsive_wrapper&quot; style=&quot;height: 100%; width: 100%; position: absolute; left: 0px; top: 0px&quot;&gt;&lt;iframe title=&quot;Wistia video player&quot; class=&quot;wistia_embed&quot; height=&quot;100%&quot; src=&quot;https://fast.wistia.net/embed/iframe/d39khp8nc0?videoFoam=true&quot; frameborder=&quot;0&quot; width=&quot;100%&quot; allowtransparency name=&quot;wistia_embed&quot; scrolling=&quot;no&quot; allowfullscreen mozallowfullscreen webkitallowfullscreen oallowfullscreen msallowfullscreen&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;&amp;nbsp; &lt;p&gt;Its built on the QuickSilver demo site and has its own catalog; the catalog is also more B2B/Industrial, containing items like Lifting Equipment, Power Tools, and Generators.  &lt;p&gt;&lt;a href=&quot;/link/2a83d15d867444f8892d29a0771e61a2.aspx&quot;&gt;&lt;img title=&quot;clip_image002&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;clip_image002&quot; src=&quot;/link/42d2a7b779d0472e886549e863c7f41b.aspx&quot; width=&quot;244&quot; height=&quot;122&quot; /&gt;&lt;/a&gt;  &lt;p&gt;Here&#39;s some of the capabilities it demonstrates:  &lt;p&gt;&#183; Organizing customers into companies (Organizations) and company divisions (Sub-Organizations)  &lt;p&gt;Customers can be associated with Organizations, representing the company. In addition, an organization can have sub-organizations, representing subdivisions.  &lt;p&gt;&lt;a href=&quot;/link/2724e134791c4bafa8102d1dd83ab485.aspx&quot;&gt;&lt;img title=&quot;clip_image004&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;clip_image004&quot; src=&quot;/link/3ed7ae4ad9a94d7ea5e6237e5267fa28.aspx&quot; width=&quot;191&quot; height=&quot;244&quot; /&gt;&lt;/a&gt;  &lt;p&gt;&#183; Customer Self Service, as a Company/Organization  &lt;p&gt;Users for a company can log in and see/manage their company&#39;s users, orders, order pads (e.g. wishlists), divisions, addresses, and other information.  &lt;p&gt;&lt;a href=&quot;/link/c2e1f592bc304e589d19277ba4833547.aspx&quot;&gt;&lt;img title=&quot;clip_image006&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;clip_image006&quot; src=&quot;/link/eb96ac772f774542b93c6a267a85ece6.aspx&quot; width=&quot;244&quot; height=&quot;114&quot; /&gt;&lt;/a&gt;  &lt;p&gt;&#183; User Impersonation  &lt;p&gt;This empowers customer users to impersonate other user&#39;s within the company.  &lt;p&gt;&lt;a href=&quot;/link/9c829d0576cd4a8893b0ede5f90e44b1.aspx&quot;&gt;&lt;img title=&quot;clip_image008&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;clip_image008&quot; src=&quot;/link/38010b6bb35b4247b8c0997e4fae3413.aspx&quot; width=&quot;244&quot; height=&quot;149&quot; /&gt;&lt;/a&gt;  &lt;p&gt;&#183; User Roles within a Company  &lt;p&gt;The site demonstrates how you can use Episerver to create admins, purchasers, and approvers with different permissions. Admins can manage the company&#39;s users, budgets, orders, etc as well as impersonate purchasers and approvers. Purchasers can&#39;t manage or see their company&#39;s information, but they can place orders. Approvers can see organization information and approve purchaser orders, allowing them to be submitted to the backend. To be clear, this is an example role setup - each B2B implementation user role setup will be unique.  &lt;p&gt;&lt;a href=&quot;/link/39fddece77184323b5240a5c6eae3c60.aspx&quot;&gt;&lt;img title=&quot;clip_image010&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;clip_image010&quot; src=&quot;/link/e17c54bfb609402285eacad045f2f751.aspx&quot; width=&quot;156&quot; height=&quot;244&quot; /&gt;&lt;/a&gt;  &lt;p&gt;&#183; Customer Credit Accounts/Budgets  &lt;p&gt;Users can manage their organization&#39;s budgets. These budgets can be assigned to a Purchaser. These budgets can then be used as a payment method during checkout.  &lt;p&gt;&lt;a href=&quot;/link/b7047e1f98754120a5c26d47dfd68203.aspx&quot;&gt;&lt;img title=&quot;clip_image012&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;clip_image012&quot; src=&quot;/link/5dfb90a1476547e29cfb9e213010e200.aspx&quot; width=&quot;244&quot; height=&quot;227&quot; /&gt;&lt;/a&gt;&lt;b&gt;&lt;/b&gt;  &lt;p&gt;&lt;b&gt;&lt;a href=&quot;/link/aa2cdcc940b84186b35cdd39b0fb2c0b.aspx&quot;&gt;&lt;img title=&quot;clip_image014&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;clip_image014&quot; src=&quot;/link/f0c2e49aaa854833a56a835dd8d32a51.aspx&quot; width=&quot;244&quot; height=&quot;130&quot; /&gt;&lt;/a&gt;&lt;/b&gt;  &lt;p&gt;&#183; Order Workflow  &lt;p&gt;Carts can be submitted by Purchasers. Orders placed by Purchaser&#39;s must be approved by Approvers to be submitted for fulfillment.  &lt;p&gt;&lt;a href=&quot;/link/232097f7616c4e068117442eba80f9e9.aspx&quot;&gt;&lt;img title=&quot;clip_image016&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;clip_image016&quot; src=&quot;/link/c21ddbfcdd09484580684d3efc85512b.aspx&quot; width=&quot;244&quot; height=&quot;91&quot; /&gt;&lt;/a&gt;  &lt;p&gt;&#183; Requesting a Quote  &lt;p&gt;Carts and Order Pads can be submitted to the backend for a quote. The quote can then be processed in the Order Management system, updating the pricing, and marking the quote as complete.  &lt;p&gt;&lt;a href=&quot;/link/9658637b0dde45cd9afedef14e98f93f.aspx&quot;&gt;&lt;img title=&quot;clip_image018&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;clip_image018&quot; src=&quot;/link/1a9d9f75854247fdb8f66946052de0a4.aspx&quot; width=&quot;244&quot; height=&quot;176&quot; /&gt;&lt;/a&gt;  &lt;p&gt;&#183; Organization-based Pricing  &lt;p&gt;This demonstrates how Episerver supports pricing based on association to an Organization. Organizations have a property, Customer Group, which maps to the pricing field Customer Price Group. Using this, you can group one or more Organizations in a Customer Group and assign a price to that Customer Group. Any users associated with that organization will get that price. To learn more about that, read here: &lt;a href=&quot;/link/3119e7b8833c4869ac2fa99551ff5ebe.aspx&quot;&gt;http://world.episerver.com/documentation/developer-guides/commerce/pricing/&lt;/a&gt;.  &lt;p&gt;&lt;a href=&quot;/link/57f3ff0d140f41e381c470f3be2753da.aspx&quot;&gt;&lt;img title=&quot;clip_image020&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;clip_image020&quot; src=&quot;/link/dc889de4e39e432d8f83791b15fec73d.aspx&quot; width=&quot;207&quot; height=&quot;244&quot; /&gt;&lt;/a&gt;  &lt;p&gt;&#183; Organization-based Discounts  &lt;p&gt;This highlights how you can create a visitor group that maps to a customer&#39;s Organization Customer Group (see previous). You can then target promotions to only apply to members of that visitor group/Organization using a setting on the Campaign settings.  &lt;p&gt;&lt;a href=&quot;/link/02b701b45afb466284a0eeabecc12703.aspx&quot;&gt;&lt;img title=&quot;clip_image022&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;clip_image022&quot; src=&quot;/link/bd7bd0a58d5e4f8b818d6b3b575800df.aspx&quot; width=&quot;244&quot; height=&quot;165&quot; /&gt;&lt;/a&gt;  &lt;p&gt;You can find the source for this B2B demo, setup directions, and user logins here : &lt;a href=&quot;http://github.com/episerver/quicksilverb2b&quot;&gt;http://github.com/episerver/quicksilverb2b&lt;/a&gt;  &lt;p&gt;It should be noted: this is a demo site, not a production-ready site. It showcases capabilities and provides code samples you can use in your implementations. The demo site also has its own business rules built-in to demonstrate a particular B2B scenario (for example, a particular order workflow, particular user permissions, the use of budgets, etc.). Of course, those underlying features can be used in plenty of implementation approaches.  &lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/Shannon-Gray/Dates/2017/4/episerver-releases-b2b-demo-site/</guid>            <pubDate>Mon, 03 Apr 2017 22:11:30 GMT</pubDate>           <category>Blog post</category></item><item> <title>Promotion Engine Sale Price Customization</title>            <link>https://world.optimizely.com/blogs/Shannon-Gray/Dates/2016/11/promotion-engine-sale-price-customization/</link>            <description>&lt;p&gt;Overriding how sale prices are calculated in Episerver’s promotion engine for SKUs and packages outside of the cart requires a minor customization. Good examples of where this applies are category landing page and product detail pages where you may show a discount price(s). Sales price calculation is usually done by a) evaluating the prices associated with a SKU or package and b) applying the site’s business rules to determine the correct price of an item for a customer. The sales price is calculated in the promotion engine to determine the price from which to subtract the promotion discounts. As I previously mentioned &lt;a href=&quot;/link/e7cbcde53eaa45689db093e009a61ea2.aspx&quot;&gt;here&lt;/a&gt;, using the PromotionEngine GetDiscountPrices() method involves an important constraint: It has a default sale price calculation built in which may or may not fit your site’s business rules. Here is how to customize the promotion engine to use alternate pricing calculation logic. &lt;p&gt;The sale price for items to be evaluated in the promotion engine is calculated in the PromotionEngineContentLoader &lt;a href=&quot;/link/55fe410f4f65482cab72fb59d47c7561.aspx?documentId=commerce/9/16DE8B32&quot;&gt;class&lt;/a&gt; by calling the ReadOnlyPricingLoader GetDefaultPrice() method. This method returns the lowest “All Customer” price for an item that has a valid date and is the correct currency and market for the current customer. So it doesn’t use customer-specific, price group, or custom sale price types to calculate the sale price. An instance of PromotionEngineContentLoader is stored in the IOC container and used in the promotion engine - so it can be overridden in your implementation.  &lt;p&gt;To override this sale price calculation in the promotion engine, create a class that inherits from PromotionEngineContentLoader and then configure your IOC container to use this new implementation. Here’s an example of how you can do this: &lt;div id=&quot;codeSnippetWrapper&quot;&gt; &lt;div id=&quot;codeSnippet&quot; style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum1&quot; style=&quot;color: #606060&quot;&gt;   1:&lt;/span&gt; &lt;span style=&quot;color: #0000ff&quot;&gt;public&lt;/span&gt; &lt;span style=&quot;color: #0000ff&quot;&gt;class&lt;/span&gt; CustomPromotionEngineContentLoader : PromotionEngineContentLoader&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum2&quot; style=&quot;color: #606060&quot;&gt;   2:&lt;/span&gt; {&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum3&quot; style=&quot;color: #606060&quot;&gt;   3:&lt;/span&gt;     &lt;span style=&quot;color: #0000ff&quot;&gt;public&lt;/span&gt; CustomPromotionEngineContentLoader(&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum4&quot; style=&quot;color: #606060&quot;&gt;   4:&lt;/span&gt;             IContentLoader contentLoader, &lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum5&quot; style=&quot;color: #606060&quot;&gt;   5:&lt;/span&gt;             CampaignInfoExtractor campaignInfoExtractor, &lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum6&quot; style=&quot;color: #606060&quot;&gt;   6:&lt;/span&gt;             ReadOnlyPricingLoader readOnlyPricingLoader) : &lt;span style=&quot;color: #0000ff&quot;&gt;base&lt;/span&gt;(contentLoader, campaignInfoExtractor, readOnlyPricingLoader){ }&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum7&quot; style=&quot;color: #606060&quot;&gt;   7:&lt;/span&gt;  &lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum8&quot; style=&quot;color: #606060&quot;&gt;   8:&lt;/span&gt;     &lt;span style=&quot;color: #0000ff&quot;&gt;public&lt;/span&gt; &lt;span style=&quot;color: #0000ff&quot;&gt;override&lt;/span&gt; IOrderGroup CreateInMemoryOrderGroup(ContentReference entryLink, IMarket market, Currency marketCurrency)&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum9&quot; style=&quot;color: #606060&quot;&gt;   9:&lt;/span&gt;     {&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum10&quot; style=&quot;color: #606060&quot;&gt;  10:&lt;/span&gt;         var orderGroup = &lt;span style=&quot;color: #0000ff&quot;&gt;new&lt;/span&gt; InMemoryOrderGroup(market, marketCurrency);&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum11&quot; style=&quot;color: #606060&quot;&gt;  11:&lt;/span&gt;  &lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum12&quot; style=&quot;color: #606060&quot;&gt;  12:&lt;/span&gt;         &lt;span style=&quot;color: #008000&quot;&gt;//this is where you can reference your own pricing calculator to retrieve the right sale price&lt;/span&gt;&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum13&quot; style=&quot;color: #606060&quot;&gt;  13:&lt;/span&gt;         IPriceValue price = YourSalePriceCalculator.GetSalePrice(entryLink);&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum14&quot; style=&quot;color: #606060&quot;&gt;  14:&lt;/span&gt;  &lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum15&quot; style=&quot;color: #606060&quot;&gt;  15:&lt;/span&gt;         &lt;span style=&quot;color: #0000ff&quot;&gt;if&lt;/span&gt; (price != &lt;span style=&quot;color: #0000ff&quot;&gt;null&lt;/span&gt;)&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum16&quot; style=&quot;color: #606060&quot;&gt;  16:&lt;/span&gt;         {&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum17&quot; style=&quot;color: #606060&quot;&gt;  17:&lt;/span&gt;             orderGroup.Forms.First().Shipments.First().LineItems.Add(&lt;span style=&quot;color: #0000ff&quot;&gt;new&lt;/span&gt; InMemoryLineItem&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum18&quot; style=&quot;color: #606060&quot;&gt;  18:&lt;/span&gt;             {&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum19&quot; style=&quot;color: #606060&quot;&gt;  19:&lt;/span&gt;                 Quantity = 1,&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum20&quot; style=&quot;color: #606060&quot;&gt;  20:&lt;/span&gt;                 Code = price.CatalogKey.CatalogEntryCode,&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum21&quot; style=&quot;color: #606060&quot;&gt;  21:&lt;/span&gt;                 PlacedPrice = price.UnitPrice.Amount&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum22&quot; style=&quot;color: #606060&quot;&gt;  22:&lt;/span&gt;             });&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum23&quot; style=&quot;color: #606060&quot;&gt;  23:&lt;/span&gt;         }&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum24&quot; style=&quot;color: #606060&quot;&gt;  24:&lt;/span&gt;  &lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum25&quot; style=&quot;color: #606060&quot;&gt;  25:&lt;/span&gt;         &lt;span style=&quot;color: #0000ff&quot;&gt;return&lt;/span&gt; orderGroup;&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum26&quot; style=&quot;color: #606060&quot;&gt;  26:&lt;/span&gt;     }&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum27&quot; style=&quot;color: #606060&quot;&gt;  27:&lt;/span&gt;          …&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum28&quot; style=&quot;color: #606060&quot;&gt;  28:&lt;/span&gt; }&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Notice that an IOrderGroup is used to contain the SKU or package. The InMemoryOrderGroup is simply an implementation of that interface that allows carts and one-off SKUs or packages to be processed for promotion calculation in the same way. This also simplifies promotion calculation. It allows for the custom promotion processors (see &lt;a href=&quot;/link/b81a2839fc0e405a9235646d271ce71e.aspx&quot;&gt;here&lt;/a&gt;) that you create to simply process the IOrderGroup instance rather than having separate methods for one-off items and carts. The PromotionProcessorContext class contains an IOrderGroup property and is passed into the Evaluate() method for promotion processor classes. 
&lt;p&gt;Finally, add initialization of your IOC container to point to your custom implementation like this:
&lt;div id=&quot;codeSnippetWrapper&quot;&gt;
&lt;div id=&quot;codeSnippet&quot; style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum1&quot; style=&quot;color: #606060&quot;&gt;   1:&lt;/span&gt; [ModuleDependency(&lt;span style=&quot;color: #0000ff&quot;&gt;typeof&lt;/span&gt;(EPiServer.Commerce.Initialization.InitializationModule))]&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum2&quot; style=&quot;color: #606060&quot;&gt;   2:&lt;/span&gt; &lt;span style=&quot;color: #0000ff&quot;&gt;public&lt;/span&gt; &lt;span style=&quot;color: #0000ff&quot;&gt;class&lt;/span&gt; DiscountCalculateInitialization : IInitializableModule, IConfigurableModule&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum3&quot; style=&quot;color: #606060&quot;&gt;   3:&lt;/span&gt; {&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum4&quot; style=&quot;color: #606060&quot;&gt;   4:&lt;/span&gt;     &lt;span style=&quot;color: #0000ff&quot;&gt;public&lt;/span&gt; &lt;span style=&quot;color: #0000ff&quot;&gt;void&lt;/span&gt; ConfigureContainer(ServiceConfigurationContext context)&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum5&quot; style=&quot;color: #606060&quot;&gt;   5:&lt;/span&gt;     {&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum6&quot; style=&quot;color: #606060&quot;&gt;   6:&lt;/span&gt;         context.Container.Configure(c =&amp;gt; c.For&amp;lt;PromotionEngineContentLoader&amp;gt;().Singleton().Use&amp;lt;CustomPromotionEngineContentLoader&amp;gt;());&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum7&quot; style=&quot;color: #606060&quot;&gt;   7:&lt;/span&gt;     }&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum8&quot; style=&quot;color: #606060&quot;&gt;   8:&lt;/span&gt; …&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;pre style=&quot;border-top-style: none; font-size: 8pt; overflow: visible; border-left-style: none; font-family: &#39;Courier New&#39;, courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4&quot;&gt;&lt;span id=&quot;lnum9&quot; style=&quot;color: #606060&quot;&gt;   9:&lt;/span&gt; }&lt;/pre&gt;&lt;!--CRLF--&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The ModuleDependency ensures that the new implementation is set/used, overriding the original implementation set in Episerver’s commerce initialization module.
&lt;p&gt;As you can see, overriding the promotion engine sale price takes very little effort. I think the new promotion engine has made calculating discounts a lot easier with a straight-forward and exposed model that can be overridden.&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/Shannon-Gray/Dates/2016/11/promotion-engine-sale-price-customization/</guid>            <pubDate>Mon, 14 Nov 2016 23:14:08 GMT</pubDate>           <category>Blog post</category></item><item> <title>Calculating Discounted Pricing With Episerver’s New Promotion Engine</title>            <link>https://world.optimizely.com/blogs/Shannon-Gray/Dates/2016/10/calculating-discounted-pricing-with-episervers-new-promotion-engine/</link>            <description>&lt;p&gt;Episerver&#39;s new promotion engine makes it easy to calculate discounts. It is also designed so that developers can easily override built-in promotion functionality and custom business rules can be applied in the calculation of discounts.&lt;/p&gt; &lt;p&gt;In working with Episerver&#39;s new promotion engine, the two most common places where you need to calculate discount prices are 1) for individual SKUs (like on a product detail page or category browse page) and 2) in the cart. &lt;/p&gt; &lt;p&gt;Calculating discounts and applying them to the cart is simple. Simply run either the CartValidate or CartPrepare activity flows as documented here:&lt;br /&gt;&lt;a href=&quot;/link/31a3464a55ab412292cc02bd382f16d3.aspx&quot;&gt;http://world.episerver.com/documentation/Items/Developers-Guide/Episerver-Commerce/9/workflows-and-activities/workflows-and-activities/&lt;/a&gt;&lt;br /&gt;Or you could use the more direct cart processing methods like cart.ApplyDiscounts() as documented here:&lt;br /&gt;&lt;a href=&quot;/link/a6be03c11f21407aab53f22a07593fcc.aspx&quot;&gt;http://world.episerver.com/documentation/Items/Developers-Guide/Episerver-Commerce/9/Orders/order-processing/&lt;/a&gt;&lt;/p&gt; &lt;p&gt;However, calculating the discounted price for a SKU/package individually involves a little more effort. Here&#39;s a quick and easy example discount calculator to do that:&lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre&gt;&lt;p&gt;private static Injected&amp;lt;ICurrentMarket&amp;gt; _currentMarket;&lt;br /&gt;private static Injected&amp;lt;IPromotionEngine&amp;gt; _promotionEngine;&lt;br /&gt;private static Injected&amp;lt;ReferenceConverter&amp;gt; _referenceConverter;&lt;br /&gt;private static Injected&amp;lt;ILineItemCalculator&amp;gt; _lineItemCalculator;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;public static decimal GetDiscountPrice(VariationContent variation, IMarket market)&lt;br /&gt;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp; if (market == null)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; market = _currentMarket.Service.GetCurrentMarket();&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp; }&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp; &lt;br /&gt;&amp;nbsp; //these discounts are returned in order of priority and only if not excluded&lt;br /&gt;&amp;nbsp; IEnumerable&amp;lt;DiscountedEntry&amp;gt; discountEntries =&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; _promotionEngine.Service.GetDiscountPrices(new List&amp;lt;ContentReference&amp;gt;() { variation.ContentLink }, market, market.DefaultCurrency, _referenceConverter.Service, _lineItemCalculator.Service);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp; //get the biggest discount promotion application and return&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp; decimal lowestDiscountedPrice = discountEntries.SelectMany(x =&amp;gt; x.DiscountPrices).OrderBy(x =&amp;gt; x.Price).FirstOrDefault().Price.Amount;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp; &lt;br /&gt;&amp;nbsp; return lowestDiscountedPrice;&lt;br /&gt;}&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;/pre&gt;
&lt;p&gt;An explanation of what this code does:&lt;br /&gt;_promotionEngine.Service.GetDiscountPrices() retrieves a list of DiscountedEntry objects. Each DiscountedEntry represents the discounted price for a SKU/package after each applicable promotion is applied. The DiscountedEntry instances reflect the applicable promotions with configured exclusions preventing inapplicable promotion discounts from being returned. In addition, the discounted prices are returned in the sort order specified in the promotion configuration UI for applicable promotions.&lt;/p&gt;
&lt;p&gt;This means, when lowestDiscountedPrice is calculated, we&#39;re limited to taking one of the discounted prices and returning it. There isn&#39;t sufficient information readily available to calculate the compounded discount price for a SKU or package (when multiple promotions are applied).&lt;/p&gt;
&lt;p&gt;GetDiscountPrices() also has a built-in price calculator to determine the sale price for the SKU or package (which is then discounted based on applicable promotions). The price calculator takes the lowest All Customers price from the SKU/packages.&lt;/p&gt;
&lt;p&gt;If these two limitations work for your site (Only one promotion can apply to a SKU or package AND the lowest All Customers price is the correct SKU/package sale price), then this is all you need to use the new promotion engine on catalog pages. The approach is also a quick-and-easy way to test that custom promotions you&#39;re creating are functioning properly.&lt;/p&gt;
&lt;p&gt;However, if this approach doesn&#39;t meet your site needs, some base functionality in GetDiscountPrices() needs to be overridden. This will be the topic of my next blog.&lt;br /&gt;&lt;/p&gt;&lt;pre&gt;&lt;/pre&gt;</description>            <guid>https://world.optimizely.com/blogs/Shannon-Gray/Dates/2016/10/calculating-discounted-pricing-with-episervers-new-promotion-engine/</guid>            <pubDate>Tue, 25 Oct 2016 07:52:49 GMT</pubDate>           <category>Blog post</category></item><item> <title>Do A Lot More With EPiServer Commerce Promotions With a Little Code</title>            <link>https://world.optimizely.com/blogs/Shannon-Gray/Dates/2013/7/Do-A-Lot-More-With-EPiServer-Commerce-Promotions-With-a-Little-Code/</link>            <description>&lt;p&gt;The promotion engine is powerful. It allows you to create a broad range of promotions with very different criteria and rewards out of the box. The Build Your Own Discount promotions are especially flexible. Here are some ways that you can extend Build Your Own Discounts to be even more powerful.&lt;/p&gt;  &lt;br /&gt;  &lt;p&gt;1. &lt;strong&gt;Promotions based on LineItem metafields&lt;/strong&gt; : its not obvious how to use LineItem metafields for promotion criteria. For example : For all LineItems in a cart where the color (a LineItem metafield in this example) is green, give $20 off the order total.&lt;/p&gt;  &lt;p&gt;2. &lt;strong&gt;Promotions based on SKU/Entry metafields : &lt;/strong&gt;they aren&#39;t exposed at all in the dynamic fields of the Build Your Own Discount promotion types. So it doesn&#39;t seem possible to provide a discount for all SKUs with the color &amp;quot;Green&amp;quot; and product types of &amp;quot;notebook&amp;quot;. &lt;/p&gt;  &lt;p&gt;3. &lt;strong&gt;Promotions based on product codes rather than SKU codes&lt;/strong&gt;: When the catalog is made up of products and SKUs, there isn&#39;t a visible way of providing discounts for products. For example, buy a shirt product (for which there are SKUs representing different colors and sizes) and get 20% off of the price for any of its associated SKUs.&lt;/p&gt;  &lt;p&gt;4. &lt;strong&gt;Promotions targeting anonymous customers based on visitor groups or other criteria: &lt;/strong&gt;Customer Segments are designed to target promotions to logged-in users. There doesn&#39;t seem to be a way to target anonymous users based on their visitor group, location information, the online marketing campaign that got them to the site, market, or any other user information.&lt;/p&gt;  &lt;br /&gt;  &lt;p&gt;&lt;strong&gt;Here&#39;s How You Can Create Each of These:&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;LineItem metafields &lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;You can create Build Your Own Discounts using the metafield values of the LineItems in a customer&#39;s cart. &lt;/p&gt;  &lt;p&gt;How to implement:&lt;/p&gt;  &lt;p&gt;* Add metafields to the LineItem metaclass for each of the fields you want to use in creating promotion criteria. &lt;/p&gt;  &lt;p&gt;* Populate the LineItem metafield when the SKU is added to the cart to create the LineItem. The code to do this looks like: &lt;/p&gt;  &lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;_cartHelper.AddEntry(entry, quantity, &lt;span class=&quot;kwrd&quot;&gt;false&lt;/span&gt;, &lt;span class=&quot;kwrd&quot;&gt;new&lt;/span&gt; CartHelper[] { _cartHelperToRemove });

&lt;span class=&quot;rem&quot;&gt;//after adding the Entry, get a reference to the lineitem and pass in the metafield value that&#39;s used by the promotion&lt;/span&gt;
LineItem item = _cartHelper.Cart.OrderForms[0].LineItems[_cartHelper.Cart.OrderForms[0].LineItems.Count - 1]; 

item[&lt;span class=&quot;str&quot;&gt;&amp;quot;Taste&amp;quot;&lt;/span&gt;] = entry.ItemAttributes[&lt;span class=&quot;str&quot;&gt;&amp;quot;Taste&amp;quot;&lt;/span&gt;].Value[0]; 

_cartHelper.Cart.AcceptChanges(); &lt;/code&gt;&lt;/pre&gt;
&lt;style type=&quot;text/css&quot;&gt;



















.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, &quot;Courier New&quot;, courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }&lt;/style&gt;

&lt;p&gt;Even better code is coming up to do this more generically..&lt;/p&gt;

&lt;p&gt;Explanation:&lt;/p&gt;

&lt;p&gt;You may have noticed that lineitem metafields are exposed in the dynamic properties in Build Your Own Discounts. This means that, when an item is added to the cart, the metafield needs to be populated in the LineItem and a promotion will apply to that LineItem where there is a promotion criteria match with that metafield. In the image below, an Order Build Your Own Discount is created with a condition that the lineitem metafield Taste be equal to a value. Note that this only works when discounts are calculated for a cart (for example with the CartValidate or CartPrepare workflows), not for a SKU on a category browse page. When you add new lineitem metafields, make sure you reset IIS so you&#39;ll see the new field(s) in Commerce Manager.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/link/6bf575e59db94e06ae440d899b32d05f.png&quot;&gt;&lt;img title=&quot;LineItemMetafield&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;LineItemMetafield&quot; src=&quot;/link/64db2698d22045069d6af96b203fbc33.png&quot; width=&quot;520&quot; height=&quot;167&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also do this:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/link/5ca94a138e9c4eed87012c6f12383355.png&quot;&gt;&lt;img title=&quot;contains&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;contains&quot; src=&quot;/link/bd096808b2184c54b6434a33726d040a.png&quot; width=&quot;525&quot; height=&quot;180&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&amp;#160; &lt;/p&gt;

&lt;p&gt;There’s one important caveat regarding using metafields (for entries or lineitems): &lt;/p&gt;

&lt;p&gt;The metafield cannot support multiple languages or be a dictionary type. It needs to be a string-type field (e.g. ShortString) and can’t be numeric. &lt;/p&gt;

&lt;p&gt;The metafield should be an internal field. For example, you may have a field &amp;quot;Color&amp;quot; which is multi-lingual and used on the front end to display different text based on the language settings for the customer. Then you&#39;d setup a secondary field &amp;quot;CLR&amp;quot; which was used strictly for internal logic purposes, like promotions. &lt;/p&gt;

&lt;p&gt;These limitations can be circumvented by using the marketing provider discussed at the end of the article.&lt;/p&gt;

&lt;p&gt;&amp;#160; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Entry metafields&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Its possible to target promotions based on Entry metafields, which would allow promotions to apply to SKUs being browsed on the site (not just items in the cart as above). To do this, associate a metafield in the lineitem class to a SKU metaclass as well. Then, when the SKU is added to the cart, make sure the cooresponding metafield is populated in the lineitem. &lt;/p&gt;

&lt;p&gt;How to implement: &lt;/p&gt;

&lt;p&gt;* Add a metafield to the LineItem metaclass (if it doesn&#39;t already exist there) &lt;/p&gt;

&lt;p&gt;* Add a metafield to the applicable SKU metaclass (if it doesn&#39;t already exist there) where you want to apply a promotion &lt;/p&gt;

&lt;p&gt;* Populate the SKU metfield value (if not already populated). &lt;/p&gt;

&lt;p&gt;* Make sure the LineItem metafield is updated with the SKU value for the same metafield when added to the cart. See the code above for the LineItem metafield promotions. &lt;/p&gt;

&lt;p&gt;Explanation:&lt;/p&gt;

&lt;p&gt;If you&#39;ve looked at the code for calculating discounts, both in Mediachase.Commerce.Website.Helpers.StoreHelper and Mediachase.Commerce.Workflow.Activities.CalculateDiscountsActivity, you&#39;ll notice that the an entry or a lineitem can be used to calculate a discount. These can be seen in :&lt;/p&gt;

&lt;p&gt;StoreHelper.GetDiscountPrice(Entry entry, string catalogName, string catalogNodeCode, IMarket market, Currency currency)&lt;/p&gt;

&lt;p&gt;and &lt;/p&gt;

&lt;p&gt;CalculateDiscountsActivity.CalculateDiscounts()&lt;/p&gt;

&lt;p&gt;Yet, when you configure a Build Your Own Discount in Commerce Manager, you only see the LineItem metafields listed as possible attributes to map promotion criteria to. Furthermore, the properties of the promotion refer to TargetLineItem. What&#39;s happening in both of the aforementioned methods is that many of the base fields and all of the metafield values of the Entry and LineItem objects are being mapped internally to a third class, PromotionEntry. When a discount price is being calculated for an Entry (like on a category landing page), the Entry metafields are added; when the discount price is calculated on a cart, the LineItem metafields are added. The PromotionEntry class doesn&#39;t know weather &amp;quot;it&amp;quot; is an Entry or a LineItem. The PromotionEntry is then used by the promotion engine to match up whether a promotion applies to a SKU or LineItem. &lt;/p&gt;

&lt;p&gt;Here&#39;s a diagram that creates a visual explanation of this: &lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/link/a1c3472600ae40279eb98e02e6ac5a76.png&quot;&gt;&lt;img title=&quot;PromotionEntry1&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;PromotionEntry1&quot; src=&quot;/link/f177644227f94b8288b2f9e320f47b14.png&quot; width=&quot;554&quot; height=&quot;306&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So you can create a promotion that targets a LineItem metafield that also happens to be shared by an Entry metaclass. If the code calls StoreHelper.GetDiscountPrice() for an Entry that has a metafield &amp;quot;CLR&amp;quot;, the LineItem metaclass also has a metafield &amp;quot;CLR&amp;quot;, and you&#39;ve created a promotion with a criteria for CLR to be equal to Green, it will match the Entry metafield value for the Entry used in the StoreHelper.GetDiscountPrice() method. &lt;/p&gt;

&lt;p&gt;Here’s some example code for adding all Entry field short string metafield values into matching Lineitem metafield values: 
  &lt;br /&gt;&lt;/p&gt;

&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;_cartHelper.AddEntry(entry, quantity, &lt;span class=&quot;kwrd&quot;&gt;false&lt;/span&gt;, &lt;span class=&quot;kwrd&quot;&gt;new&lt;/span&gt; CartHelper[] { _cartHelperToRemove });

&lt;span class=&quot;rem&quot;&gt;//after adding the Entry, get a reference to the LineItem and pass &lt;/span&gt;
&lt;span class=&quot;rem&quot;&gt;//in the metafield value that&#39;s used by the promotion&lt;/span&gt;

… 
lineItemBeingAdded = _cartHelper.Cart.OrderForms[0].LineItems[_cartHelper.Cart.OrderForms[0].LineItems.Count - 1]; 

MapEntrytoLineItem(entry, lineItemBeingAdded);
…

&lt;span class=&quot;rem&quot;&gt;/// &amp;lt;summary&amp;gt; &lt;/span&gt;
&lt;span class=&quot;rem&quot;&gt;/// Map all matching SKU metafield values (strings only) to matching lineItem metafields &lt;/span&gt;
&lt;span class=&quot;rem&quot;&gt;/// &amp;lt;/summary&amp;gt; &lt;/span&gt;
&lt;span class=&quot;rem&quot;&gt;/// &amp;lt;param name=&amp;quot;entry&amp;quot;&amp;gt;&amp;lt;/param&amp;gt; &lt;/span&gt;
&lt;span class=&quot;rem&quot;&gt;/// &amp;lt;param name=&amp;quot;item&amp;quot;&amp;gt;&amp;lt;/param&amp;gt; &lt;/span&gt;
&lt;span class=&quot;kwrd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kwrd&quot;&gt;void&lt;/span&gt; MapEntrytoLineItem(Entry entry, LineItem item) 
{ 
   &lt;span class=&quot;kwrd&quot;&gt;foreach&lt;/span&gt; (ItemAttribute entryAttribute &lt;span class=&quot;kwrd&quot;&gt;in&lt;/span&gt; entry.ItemAttributes.Attribute) 
   { 
     &lt;span class=&quot;kwrd&quot;&gt;foreach&lt;/span&gt; (MetaField lineitemAttribute &lt;span class=&quot;kwrd&quot;&gt;in&lt;/span&gt; item.MetaClass.MetaFields) 
     { 
       &lt;span class=&quot;kwrd&quot;&gt;if&lt;/span&gt; (lineitemAttribute.Name.Equals(entryAttribute.Name)) 
       { 
         &lt;span class=&quot;kwrd&quot;&gt;if&lt;/span&gt; (entryAttribute.Type.Equals(&lt;span class=&quot;str&quot;&gt;&amp;quot;ShortString&amp;quot;&lt;/span&gt;, StringComparison.InvariantCultureIgnoreCase)) 
         { 
           item[lineitemAttribute.Name] = entry.ItemAttributes[entryAttribute.Name].Value[0]; 
         } 
       } 
     } 
  } 
}&lt;/code&gt;&lt;/pre&gt;
&lt;style type=&quot;text/css&quot;&gt;




















.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, &quot;Courier New&quot;, courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }&lt;/style&gt;

&lt;p&gt;&amp;#160; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Promotions With Product Criteria&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;If a catalog contains products and SKUs, the current approach to target items in that catalog is either limited to a catalog, category code, or SKU - there isn&#39;t a clear way to target products.&lt;/p&gt;

&lt;p&gt;This is important if you want to simplify creating Build Your Own Discounts. For example, if you want to target 5 products that have 30 associated SKUs - that&#39;s a tedious build your own discount to create. However, there is a way to create promotion criteria that map to product codes. To add this, you can simply add a metafield to the SKU metaclass definition, call it something like ProductCode, and populate it with the parent product code. Setting the metafield value can either be done to the database definition of the SKU or dynamically, during runtime, immediately before calling the StoreHelper.GetDiscountPrice() or running one of the workflows. There also needs to be a matching product code metafield on the LineItem metaclass. And as before, the Entry metafield value needs to be mapped to the lineitem metafield when the item is added to the cart. Then you can write promotions that target products.&lt;/p&gt;

&lt;p&gt;&amp;#160; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customer Targeting&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you want to target particular customers for your promotions, you&#39;re currently seemingly limited to customer segment configuration in promotions and promotion campaigns. And customer segments require the user to be logged in and to have provided information like their email or address.&lt;/p&gt;

&lt;p&gt;However, there is a way to inject customer information into a SKU or lineitem to allow that information to be used as a promotion criteria. To do this, you need to add a metafield to the Entry object in memory before calling StoreHelper.GetDiscountPrice() or executing a workflow.&lt;/p&gt;

&lt;p&gt;The ItemAttributes portion of the Entry object is settable. So you can retrieve the built-in collection of metafield values from ItemAttributes and add your own metafields and values. So you could do something like:&lt;/p&gt;

&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;List&amp;lt;ItemAttribute&amp;gt; attributeList = &lt;span class=&quot;kwrd&quot;&gt;new&lt;/span&gt; List&amp;lt;ItemAttribute&amp;gt;(entry.ItemAttributes.Attribute); 
ItemAttribute visitorGroupAttribute = &lt;span class=&quot;kwrd&quot;&gt;new&lt;/span&gt; ItemAttribue();
visitorGroupAttribute.FriendlyName = &lt;span class=&quot;str&quot;&gt;&amp;quot;VisitorGroup&amp;quot;&lt;/span&gt;; 
visitorGroupAttribute.Name = &lt;span class=&quot;str&quot;&gt;&amp;quot;VisitorGroup&amp;quot;&lt;/span&gt;; 
visitorGroupAttribute.Type = &lt;span class=&quot;str&quot;&gt;&amp;quot;ShortString&amp;quot;&lt;/span&gt;;

&lt;span class=&quot;rem&quot;&gt;//the value set in this metafield can be a visitor group lookup or other customer-attributes &lt;/span&gt;
visitorGroupAttribute.Value = &lt;span class=&quot;kwrd&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;kwrd&quot;&gt;string&lt;/span&gt;[] { &lt;span class=&quot;str&quot;&gt;&amp;quot;MyVisitorGroup&amp;quot;&lt;/span&gt; };

attributeList.Add(visitorGroupAttribute); 
entry.ItemAttributes.Attribute = attributeList.ToArray(); 

var discountPrice = StoreHelper.GetDiscountPrice(entry, &lt;span class=&quot;kwrd&quot;&gt;string&lt;/span&gt;.Empty, &lt;span class=&quot;kwrd&quot;&gt;string&lt;/span&gt;.Empty, _marketId);&lt;/code&gt;&lt;/pre&gt;
&lt;style type=&quot;text/css&quot;&gt;



















.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, &quot;Courier New&quot;, courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }&lt;/style&gt;

&lt;p&gt;To be able to use this new metafield, you&#39;d need to add a new metafield named &amp;quot;VisitorGroup&amp;quot; to the LineItem metaclass. When the entry is added to the cart, make sure the LineItem metafield is populated. Now you&#39;ll be able to create Build Your Own Discounts that can target different anonymous users based on their associated Visitor Groups or geo-location lookup location or whatever other criteria you want to you. &lt;/p&gt;

&lt;p&gt;&amp;#160; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extending the Promotion Engine Further&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There&#39;s another alternative to implementing this logic. You can control how the PromotionEntry object is populated in the StoreHelper.GetDiscountPrice() method and CalculateDiscountActivity before its used to determine whether an Entry or Cart or LineItem meets a promotion&#39;s criteria. The EPiServer Commerce system has a provider model for populating the PromotionEntry object. It allows you to explicitly set what fields are added to the PromotionEntry object. &lt;/p&gt;

&lt;p&gt;&amp;#160; &lt;/p&gt;
&lt;a href=&quot;/link/2b16b6a0f88246c6a9167a1b58ea02f1.png&quot;&gt;&lt;img title=&quot;PromotionEntry2&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;PromotionEntry2&quot; src=&quot;/link/1833030a2a6c4f8bbf8c2e9d3b92efd5.png&quot; width=&quot;554&quot; height=&quot;278&quot; /&gt;&lt;/a&gt; 

&lt;p&gt;&amp;#160; &lt;/p&gt;

&lt;p&gt;This approach allows you to circumvent the aforementioned limitations where non-string values don&#39;t work in promotions and dictionaries can&#39;t be used. The reason non-string values don&#39;t currently work is because the string representation of all metafield values is currently injected into the PromotionEntry object. However, in your own implementation, you can pass the correct data types in for those metafields you want to use in your promotions. Once you&#39;ve done this, you&#39;ll be able to do things like create a criteria where the container size is greater than 10. &lt;/p&gt;

&lt;p&gt;You can use the marketing provider model to :&lt;/p&gt;

&lt;p&gt;- add customer segment information like visitor groups or online marketing campaign ids &lt;/p&gt;

&lt;p&gt;- decide what dictionary values are added and in what format&lt;/p&gt;

&lt;p&gt;- decide what localized version of a field is used in the PromotionEntry &lt;/p&gt;

&lt;p&gt;- add values in their explicit type &lt;/p&gt;

&lt;p&gt;- create composite fields (using the data from several metafields and/or data sources to create a custom field value)&lt;/p&gt;

&lt;p&gt;- and other things no one has thought of.&lt;/p&gt;

&lt;p&gt;To use this provider model: &lt;/p&gt;

&lt;p&gt;* Create a new class project &lt;/p&gt;

&lt;p&gt;* Add a new class to this project that inherits from IPromotionEntryPopulate. (See an example implementation &lt;a href=&quot;http://world.episerver.com/Code/Shannon-Gray/Promotion-PromotionEntry-Provider-Implementation-Example/&quot;&gt;here&lt;/a&gt;) &lt;/p&gt;

&lt;p&gt;* Add your custom settings to the IPromotionEntryPopulate implementation &lt;/p&gt;

&lt;p&gt;* Create a reference in your web site to this new project &lt;/p&gt;

&lt;p&gt;* Modify the Configs/ecf.marketing.config file (PromotionEntryPopulateFunctionType element) to map to this new dll and class &lt;/p&gt;

&lt;p&gt;* Create LineItem metafields that map to these new settings&lt;/p&gt;

&lt;p&gt;* If using this with SKU promotions too, then add the metafields to the SKU metaclass as well. And populate the LineItems with the SKU values when they&#39;re added to the cart. &lt;/p&gt;

&lt;p&gt;* Now you can create build your own discount promotions that use these custom properties. &lt;/p&gt;

&lt;p&gt;An example implementation of the IPromotionEntryPopulate is provided here: &lt;a href=&quot;http://world.episerver.com/Code/Shannon-Gray/Promotion-PromotionEntry-Provider-Implementation-Example/&quot;&gt;http://world.episerver.com/Code/Shannon-Gray/Promotion-PromotionEntry-Provider-Implementation-Example/ &lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It uses the implementation built-in to the API with some example added fields. There&#39;s not a lot to explain in the implementation. The class simply passes values from either a LineItem or an Entry object into the PromotionEntry dictionary (&lt;string object=&quot;&quot; =&quot;&quot;&gt;). Add your custom settings there.&lt;/string&gt;&lt;/p&gt;

&lt;p&gt;All of these approaches will take more time thinking about and designing then any of the coding will take. Hopefully this help you create more customized promotions quickly and easily in EPiServer Commerce.&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/Shannon-Gray/Dates/2013/7/Do-A-Lot-More-With-EPiServer-Commerce-Promotions-With-a-Little-Code/</guid>            <pubDate>Tue, 30 Jul 2013 06:22:55 GMT</pubDate>           <category>Blog post</category></item><item> <title>EPiServer Commerce Promotion Examples</title>            <link>https://world.optimizely.com/blogs/Shannon-Gray/Dates/2013/6/EPiServer-Commerce-Promotion-Examples/</link>            <description>&lt;p&gt;Sometimes the best way to learn how something works is to see some examples of how its used. Below are a number of promotion examples built in EPiServer Commerce (out of the box) that will illustrate how dynamic promotions work. Specifically, you’ll see how to customize the Build Your Own Discount promotion templates to create a wide array of discounts. They come in three flavors : Entry (for giving discounts on individuals SKUs, independent of the cart, like on a category landing page), Order (for cart discounts on the cart or items in the cart), and Shipping (for providing discounts on shipping charges). Here are the examples with some explanation.&lt;/p&gt;  &lt;p&gt;One general note is that the promotion descriptions are in $ but aren’t specific to a currency (unless specifically noted). &lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;For orders which contain more than $60 of a combination of one or more skus, give a free gift&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;[Order: Building Your Own Discount]&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;/link/c038406fe6664b069ef4e36340dc0f27.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;/link/3a7ade0d9c7844cda251e562a1ca274e.png&quot; width=&quot;514&quot; height=&quot;340&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;The ExtendedPrice of LineItems represents the total cost for a “row” in a cart. For example, if you are getting 3 bottles of wine at $10 each, your ExtendedPrice would be $30. CatalogEntryId is the code or ID associated with a SKU. This term is a little confusing because it is *not* the CatalogEntryId of the SKU. The hierarchy of the dynamic purchase conditions means that the condition looks for a combination of lineitems in the cart which match an ExtendedPrice sum of 60 and any combination of the SKUs that matched the CatalogEntryIDs separated by OR statements. If the ORs weren’t present, it would require all three SKUs to be present in the cart and have a total sum of extended prices more than $60.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Buy 3 or more of a combination of one or more skus, give $5 off for a particular sku for the month of May only. This is limited to the first two quantity of the particular sku.&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;[Order: Building Your Own Discount]&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;/link/b0e9172f285c401594382693fecba769.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;/link/2b1f5db664c74a878d4c88e4fdfa9584.png&quot; width=&quot;492&quot; height=&quot;520&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;This essentially says : For any combination of SKUs with a total quantity of 3 and have any combination of the following SKU codes, give the following reward.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Buy more than $25 in an order, give 10% off and free shipping&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;This is really two promotions : an Order promotion and a Shipping promotion. If your promotion is complicated, consider breaking it into multiple promotions.&lt;/p&gt;  &lt;p&gt;[Order: Building Your Own Discount] &lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;/link/0d04bb2153574427a593a8f896be36f4.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;/link/5093be5faeca4899b39c83b19d344f22.png&quot; width=&quot;498&quot; height=&quot;224&quot; /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;[Shipping: Building Your Own Discount]&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;/link/669f2ee8161f4c31b4b18ef606e76cf0.png&quot;&gt;&lt;img title=&quot;freeShipping&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;freeShipping&quot; src=&quot;/link/9630a9350dac491c8c85142eaba39f5c.png&quot; width=&quot;498&quot; height=&quot;253&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Notice this uses Subtotal. Subtotal is the total of all of the ExtendedPrice values in the cart. It doesn’t include shipping charges or taxes. Note that the exception with taxes is that, if taxes are included in the SKU prices, as they often are with VAT, the tax amount won’t be separated from the SKU price. The property Total is available to provide a discount against the the total of the order including shipping… but that isn’t usually the intent of the promotion.&lt;/p&gt;  &lt;p&gt;Also notice that the shipping promotion accesses the cart subtotal a little differently than the Order promotion.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Buy either an decanter or a glass and get 2 free bottles of a particular wine.&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;[Order: Building Your Own Discount]&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;/link/21468d83a80d471e969e27256eb0fdeb.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;/link/a3ff94737b5b4a349dbe72201c244363.png&quot; width=&quot;521&quot; height=&quot;461&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;The CatalogEntryIds here are for decanters and glasses. This is in contrast with the next promotion..&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Buy a decanter and at least two glasses, get a free bottle of wine&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;[Order: Building Your Own Discount]&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;/link/5b047d39198b424cbd4cbb3cdff008b8.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;/link/7310decf65674931b5229519938f0efd.png&quot; width=&quot;518&quot; height=&quot;359&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;In this case, the first conditions of the purchase condition contains two decanter SKU codes and the second OrderForm.LineItems conditions contain two wine glass SKU codes. When two conditions are stacked without an Or statement between (as they are here, OrderForm.LineItems with children on top of another OrderForm.LineItems), they are assumed to be AND.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Buy more than 10 items, get 20% off of all of the items in the cart&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;[Order: Building Your Own Discount]&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;/link/ed34f4d61eff440385535fa9faa4d7ad.png&quot;&gt;&lt;img title=&quot;clip_image001[4]&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;clip_image001[4]&quot; src=&quot;/link/ca69342e6c5f4d44a0e2b91258f53c9d.png&quot; width=&quot;514&quot; height=&quot;274&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Here,&amp;#160; the sum of the quantities is great than 10, meaning the total quantities of all of the lineitems. Gift items won’t be included in the count here. &lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Buy more than 20€ of an item, get 10% off of order &lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;This allows you to target the discount to a particular currency.&lt;/p&gt;  &lt;p&gt;[Order: Build Your Own Discount]&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;/link/7739d946738940f4bf3bcc951443b8d7.jpg&quot;&gt;&lt;img title=&quot;clip_image002&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;clip_image002&quot; src=&quot;/link/5a46d95a70554fb5b4ff6fa007509c1b.jpg&quot; width=&quot;504&quot; height=&quot;275&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Note that this is for the cart, not items on a category page as they haven’t been associated with your cart – so BillingCurrency won’t be available for promotion discounts in the Entry : Build Your Own Discount.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Give $20 off anything in a category&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;[Entry: Build Your Own Discount]&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;/link/c1b4cf9518bf44eab4bbe6c7c2cae3ab.jpg&quot;&gt;&lt;img title=&quot;clip_image002[5]&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;clip_image002[5]&quot; src=&quot;/link/3d3b7ab79fcb424cbcd904b9deb9d466.jpg&quot; width=&quot;497&quot; height=&quot;230&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;This could be used with any type of Build Your Own Discount. It will also apply to SKUs in a category page/detail page.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Buy 3 items from one category or 1 from another (with a value great than $100), get $20 back&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;[Order: Build Your Own Discount]&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;/link/524eb662a73a41a5986713e3bc2a76f5.jpg&quot;&gt;&lt;img title=&quot;clip_image002[1]&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;clip_image002[1]&quot; src=&quot;/link/a0a1521df89d4f8caf1da642bc2ef476.jpg&quot; width=&quot;501&quot; height=&quot;379&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Important note : When you specify a % or $ off whole order reward, the reward will be calculated as a discount off the subtotal, not total.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Buy an item worth more than $100, get free shipping&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;[Shipping : Build Your Own Discount]&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;/link/4eafa752438d4cda9033948299f2633a.jpg&quot;&gt;&lt;img title=&quot;clip_image002[3]&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;clip_image002[3]&quot; src=&quot;/link/1119db10dd3c48f091e38e10df94062e.jpg&quot; width=&quot;498&quot; height=&quot;255&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;One final note : make sure to double check your promotion settings. Not only is it easy to make errors, but often the dynamic controls are quirky and don’t record or save the values you’ve entered. Its always worthwhile re-opening a promotion after creation to confirm you set it and the application saved it properly. &lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/Shannon-Gray/Dates/2013/6/EPiServer-Commerce-Promotion-Examples/</guid>            <pubDate>Sat, 29 Jun 2013 03:01:12 GMT</pubDate>           <category>Blog post</category></item><item> <title>Credit Card Number Storage and PCI</title>            <link>https://world.optimizely.com/blogs/Shannon-Gray/Dates/2013/6/Credit-Card-Number-Storage-and-PCI/</link>            <description>  &lt;p&gt;&lt;em&gt;&lt;font size=&quot;1&quot;&gt;The opinions, estimates and projections in this article accurately constitute the current judgment and culmination of views of industry practices at the date of the article. They do not form any form of warranty, assurance or advice and do not necessarily reflect the opinions of EPiServer AB and are subject to change without notice.&lt;/font&gt;&lt;/em&gt;&lt;/p&gt;    &lt;p&gt;&lt;em&gt;[This is an edited version of the original]&lt;/em&gt;&amp;#160; &lt;br /&gt;Storing credit card details in a database is obviously a very sensitive issue because of the threat of credit card data theft. So the question naturally arises - what should one do with credit card information in your implementation? All too often, the issue of credit card security has been overlooked on a rushed site and its later discovered that credit card information is accidentally stored unencrypted in the commerce database. Credit card data compromise is a big liability (in the form of penalties and bad press) that should be addressed early on in the design phase of developing a commerce site - regardless of the framework used to build an ECommerce site. EPiServer Commerce *enables* you to be PCI-compliant... but it doesn&#39;t ensure it without action and planning on the part of those designing and building the site. This blog is to introduce PCI and cover one way to reduce your PCI-compliance exposure - namely how to ensure that credit card information is never stored in your EPiServer Commerce system. &lt;/p&gt;      &lt;p&gt;&lt;strong&gt;What is PCI?&lt;/strong&gt;     &lt;br /&gt;Because of the security issues surrounding credit cards, a standard called Payment Card Industry Data Security Standard, commonly referred to as PCI (or PCI DSS), was developed by the major credit card processors through the PCI Security Standards Council (&lt;a href=&quot;http://www.pcisecuritystandards.org&quot;&gt;www.pcisecuritystandards.org&lt;/a&gt;). The PCI standard requires a number of security measures be in place for any business that handles credit card information to ensure the data is well protected from theft. PCI compliance is a requirement in using payment providers to process credit card payments. &lt;/p&gt;    &lt;p&gt;The PCI standard consists of 12 requirements that pertain to the processing, storing, and transmission of credit card information. The requirements cover how credit card information should to be handled including database encryption, encrypting data transmission (SSL), system access management practices, system modification logging, firewall configuration, anti-virus software, and physical access to hardware containing credit card information. There are also different levels of monitoring and reporting required based on the number of credit card transactions that a business handles annually. The PCI standard isn&#39;t limited to web site credit card processing - it relates to all credit card handling and processing. That&#39;s a really high-level overview of PCI and there are a lot of great sites and articles that provide much great insight and direction for how to be PCI compliant (see below for several of those links).&lt;/p&gt;  &lt;p&gt;&amp;#160; &lt;br /&gt;Note that, in addition to PCI compliance, there are also often state and national laws regarding reporting credit card data compromise depending on where consumers who purchase items/services from your site live. Its also important to be be aware of and, of course, comply with these requirements. The European Data Protection Act and personal data theft reporting laws in almost all US states are some examples of this.     &lt;br /&gt;&lt;/p&gt;          &lt;p&gt;&lt;strong&gt;Is EPiServer Commerce PCI-Compliant?&lt;/strong&gt;     &lt;br /&gt;As stated above, EPiServer Commerce enables you to be PCI-compliant (&lt;a href=&quot;http://www.episerver.com/Commerce/Security/)&quot;&gt;http://www.episerver.com/Commerce/Security/)&lt;/a&gt;. However, no software comes with hardware access restriction policies or firewall configuration or most of what is in the PCI standard. PCI compliance is really a more holistic view of how your business practices can ensure credit card security, rather than simply whether your deployed site and database meets the standard. Its safe to say that storing credit card information unencrypted in your Commerce database is not PCI-compliant. &lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;strong&gt;How credit card data should be handled in EPiServer Commerce sites&lt;/strong&gt;     &lt;br /&gt;In most cases, credit card information shouldn&#39;t be stored in the database at any time. Storing this information is not only a liability for security reasons, but it results in more compliance actions that need to be taken to be PCI-compliant. Credit card information is stored by the credit card provider you integrate with (Paypal, DataCash, DIBS, etc), which are required to be PCI-compliant. There are ways that these providers allow you to access those cards for processing later in certain circumstances (more on this later). Simply using a third-party credit card processing company is not sufficient to ensure full PCI-compliance by itself - but it reduces the number of measures you&#39;ll need to implement to be PCI compliant.&lt;/p&gt;      &lt;p&gt;&amp;#160; &lt;br /&gt;&lt;strong&gt;The Technical Stuff&lt;/strong&gt;     &lt;br /&gt;First a little background on how credit card information is processed in EPiServer Commerce…Some basics on this topic include:     &lt;br /&gt;Order System documentation:     &lt;br /&gt;&lt;a href=&quot;http://sdk.episerver.com/commerce/1.1.2/Content/Developers%20Guide/Order%20System/Order%20System.htm&quot;&gt;http://sdk.episerver.com/commerce/1.1.2/Content/Developers%20Guide/Order%20System/Order%20System.htm&lt;/a&gt;     &lt;br /&gt;Aspects of checkout in EPiServer Commerce:     &lt;br /&gt;&lt;a href=&quot;http://sdk.episerver.com/commerce/1.1.2/Content/Developers%20Guide/Order%20System/devOrderSystem/Checkout%20Process/Checkout%20Process.htm&quot;&gt;http://sdk.episerver.com/commerce/1.1.2/Content/Developers%20Guide/Order%20System/devOrderSystem/Checkout%20Process/Checkout%20Process.htm&lt;/a&gt;     &lt;br /&gt;Order system explanation by Roger Cevung:     &lt;br /&gt;&lt;a href=&quot;http://world.episerver.com/Articles/Items/Fundamental-Order-System-Classes-in-EPiServer-Commerce/&quot;&gt;http://world.episerver.com/Articles/Items/Fundamental-Order-System-Classes-in-EPiServer-Commerce/&lt;/a&gt;     &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;Fundamentally, the most important task in preventing credit card data from being saved to the database is to clear out the CreditCardPayment properties containing sensitive data after processing the payments and prior to saving the cart. If you use the EPiServer Commerce payment provider model, payment processing occurs when the CartCheckout workflow is executed. At no time during the workflow is data saved to the database (unless a custom activity is added in an implementation which does this, which is counter to the design of the workflow). The CartCheckout workflow has activities in it to process order payment with the associated payment provider, update catalog item inventory, and update promotion usage properties. After the workflow is run, the sensitive data must be set to innocuous values. Only then should the cart be saved/updated in the database.    &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;Here&#39;s a code example that demonstrates this:    &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;&amp;#160; //put credit card details in the cart    &lt;br /&gt;&amp;#160; CreditCardPayment pymt = new CreditCardPayment();     &lt;br /&gt;&amp;#160; pymt.CardType = ddlCardType.SelectedValue;     &lt;br /&gt;&amp;#160; pymt.CreditCardNumber = txtCardNumber.Text;     &lt;br /&gt;&amp;#160; //... other details added     &lt;br /&gt;&amp;#160; ch.Cart.OrderForms[0].Payments.Add(pymt);     &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160; //It&#39;s vital that a try/catch wrapper be put around this statement to    &lt;br /&gt;&amp;#160; //ensure that the credit card details are *always* cleared     &lt;br /&gt;&amp;#160; try     &lt;br /&gt;&amp;#160; {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; //now the payments can be processed and the order completed     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; ch.RunWorkflow(OrderGroupWorkflowManager.CartCheckOutWorkflowName);     &lt;br /&gt;&amp;#160; }     &lt;br /&gt;&amp;#160; catch (Exception ex)     &lt;br /&gt;&amp;#160; {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; //whatever other handling of errors you need to add...&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; //if another exeception is going to be thrown in this &#39;catch&#39; to exit&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; //this method, call the ClearCCDetails method just to be sure the credit&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; //card details are removed     &lt;br /&gt;&amp;#160; } &lt;/p&gt;                    &lt;p&gt;&amp;#160; //This method removes sensitive credit card data    &lt;br /&gt;&amp;#160; ClearCCDetails(ch.Cart);     &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;&amp;#160; //now you can finally save the order to the database    &lt;br /&gt;&amp;#160; ch.Cart.AcceptChanges();     &lt;br /&gt;} &lt;/p&gt;    &lt;p&gt;   &lt;br /&gt;private void ClearCCDetails(Cart cart)     &lt;br /&gt;{     &lt;br /&gt;&amp;#160; //clear out the credit card details for all credit card payments in the order     &lt;br /&gt;&amp;#160; foreach (Payment payment in cart.OrderForms[0].Payments)     &lt;br /&gt;&amp;#160; {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; CreditCardPayment cc = payment as CreditCardPayment;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; if (cc != null)     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; cc.CreditCardNumber = string.Empty;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; cc.CardType = string.Empty;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; cc.CreditCardSecurityCode = string.Empty;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; cc.CustomerName = string.Empty;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; cc.ExpirationMonth = 0;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; cc.ExpirationYear = 0;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }     &lt;br /&gt;&amp;#160; }     &lt;br /&gt;} &lt;/p&gt;    &lt;p&gt;   &lt;br /&gt;Deleting this information from the order naturally raises a few concerns :     &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;&lt;strong&gt;But what about showing the last 4 digits on the &amp;quot;thank you&amp;quot; page?      &lt;br /&gt;&lt;/strong&gt;If you need to do this, one way to handle this is to put that information in the session before deleting it. After its retrieved in the thank you page, make sure to empty the session variable. For example:     &lt;br /&gt;...     &lt;br /&gt;pymt.CreditCardNumber = txtCardNumber.Text;     &lt;br /&gt;ch.Cart.OrderForms[0].Payments.Add(pymt);     &lt;br /&gt;string lastFour = pymt.CreditCardNumber;     &lt;br /&gt;lastFour = string.Format(&amp;quot;{0}{1}&amp;quot;, (string.Empty).PadLeft(lastFour.Length - 4, &#39;*&#39;),     &lt;br /&gt;&amp;#160; lastFour.Substring(lastFour.Length - 4));     &lt;br /&gt;Session[&amp;quot;LastFour&amp;quot;] = lastFour; &lt;/p&gt;    &lt;p&gt;   &lt;br /&gt;&lt;strong&gt;How about getting access to that credit card information later for charging the card (when only authorization is done during checkout)?      &lt;br /&gt;&lt;/strong&gt;This is the purpose of the TransactionId response property associated with the payment provider transaction. The built-in providers (e.g. Klarna, Authorize.NET, etc) already accept that TransactionId property from successful credit card transactions and set it in the CreditCardPayment.TransactionId property. This value shouldn&#39;t be blanked out before saving the cart to the database - it should be retained (and is not considered sensitive credit card information). This is the reference id that allows you to do a capture on a credit card after an authorization. This TransactionId field maps to the credit card details stored by the payment provider. It&#39;s not a given that this is supported by all payment providers (and in this fashion) so check the provider&#39;s documentation.     &lt;br /&gt;To execute the charge aka capture of the credit card at a later time, the provider can be called, passing in the transactionid rather than the other credit card details with a transaction type of capture. &lt;/p&gt;    &lt;p&gt;   &lt;br /&gt;&lt;strong&gt;What about subscriptions?&lt;/strong&gt;     &lt;br /&gt;How this is handled depends on the payment handler. Here are the two ways I&#39;ve seen this handled:     &lt;br /&gt;* Some payment providers allow you to setup a recurring payment order through the API; once setup, the payment provider automatically performs the regular billing per the schedule specified. Of course, you can alter or cancel the recurring payment; however the recurring payments are then automatically managed by the payment provider. This option is great if you have pretty straight-forward monthly rates (including free trial periods) and want all of the charging done independently from your system. These offerings can include rich administrative interfaces and features to manage those subscriptions. Some examples of this are Paypal (&lt;a href=&quot;https://www.x.com/developers/paypal/documentation-tools/express-checkout/integration-guide/ECGettingStarted)&quot;&gt;https://www.x.com/developers/paypal/documentation-tools/express-checkout/integration-guide/ECGettingStarted)&lt;/a&gt;     &lt;br /&gt;(which also offers the other option below) and WorldPay (&lt;a href=&quot;http://www.worldpay.com/support/kb/bg/pdf/rpfp.pdf)&quot;&gt;http://www.worldpay.com/support/kb/bg/pdf/rpfp.pdf)&lt;/a&gt; &lt;/p&gt;    &lt;p&gt;   &lt;br /&gt;* Other payment providers allow you to store an id representing the credit card (rather than a TransactionId representing a transaction) and periodically charge that credit card from your own system. This may be better if you want to more tightly manage the billing and if your monthly charges vary. Some examples of this are DIBS (&lt;a href=&quot;http://tech.dibspayment.com/dibs_api/flexwin/payment_functions/ticket_authcgi/)&quot;&gt;http://tech.dibspayment.com/dibs_api/flexwin/payment_functions/ticket_authcgi/)&lt;/a&gt;, Paypal (&lt;a href=&quot;https://www.x.com/developers/paypal/documentation-tools/paypal-payments-pro/integration-guide/WPWebsitePaymentsPro)&quot;&gt;https://www.x.com/developers/paypal/documentation-tools/paypal-payments-pro/integration-guide/WPWebsitePaymentsPro)&lt;/a&gt;, and Authorize.NET (&lt;a href=&quot;http://developer.authorize.net/api/arb/)&quot;&gt;http://developer.authorize.net/api/arb/)&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;strong&gt;What if I want to store and display the last four digits of a user’s credit cards so customers can select them for reuse during checkout?      &lt;br /&gt;&lt;/strong&gt;There is considerable discussion about whether the PCI standard allows the last four digits of the credit card number (also known as the Primary Account Number or PAN) to be stored in the database without encryption. This is referred to as protecting the PAN through truncation. This is commonly used to display the stored credit card options to a customer from previous purchases. Also note that some payment providers return a token (Cybersource and Braintree are two examples) after processing a credit card transaction representing the credit card (see the previous topic) which contains the last four digits of the credit card. So you could use these tokens to render the last four digits of the users’ credit card PAN. However, there are implications for storing the last four digits of the PAN and you should consult the PCI guides, your payment processor agreement, and your PCI experts regarding this topic. &lt;/p&gt;      &lt;p&gt;A few other tips:    &lt;br /&gt;* When testing a storefront, you should not use live credit card data but instead use test credit cards provided by your payment provider.     &lt;br /&gt;* Don&#39;t log credit card details (even in testing)&lt;/p&gt;      &lt;p&gt;And finally, here are some resources related to this topic:    &lt;br /&gt;EPiServer links     &lt;br /&gt;Use SSL for data transmission in checkout     &lt;br /&gt;&lt;a href=&quot;http://world.episerver.com/Documentation/Items/Tech-Notes/EPiServer-CMS-6/EPiServer-CMS-60/Securing-Edit-and-Admin/&quot;&gt;http://world.episerver.com/Documentation/Items/Tech-Notes/EPiServer-CMS-6/EPiServer-CMS-60/Securing-Edit-and-Admin/&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;a href=&quot;http://world.episerver.com/Documentation/Items/Tech-Notes/EPiServer-CMS-6/EPiServer-CMS-60/Protecting-Your-Site-From-Session-Hijacking/&quot;&gt;http://world.episerver.com/Documentation/Items/Tech-Notes/EPiServer-CMS-6/EPiServer-CMS-60/Protecting-Your-Site-From-Session-Hijacking/&lt;/a&gt;     &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;PCI Documents    &lt;br /&gt;&lt;a href=&quot;https://www.pcisecuritystandards.org/security_standards/documents.php&quot;&gt;https://www.pcisecuritystandards.org/security_standards/documents.php&lt;/a&gt;     &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;PCI Quick Reference    &lt;br /&gt;&lt;a href=&quot;https://www.pcisecuritystandards.org/documents/PCI%20SSC%20Quick%20Reference%20Guide.pdf&quot;&gt;https://www.pcisecuritystandards.org/documents/PCI%20SSC%20Quick%20Reference%20Guide.pdf&lt;/a&gt;&lt;/p&gt;            &lt;p&gt;&lt;em&gt;&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;&lt;em&gt;Remember that this blog doesn&#39;t constitute a warranty, assurance or advice.&lt;/em&gt;&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/Shannon-Gray/Dates/2013/6/Credit-Card-Number-Storage-and-PCI/</guid>            <pubDate>Wed, 12 Jun 2013 20:19:54 GMT</pubDate>           <category>Blog post</category></item><item> <title>EPiServer Commerce Order Search Made Easy</title>            <link>https://world.optimizely.com/blogs/Shannon-Gray/Dates/2012/12/EPiServer-Commerce-Order-Search-Made-Easy/</link>            <description>&lt;p&gt;Searching for orders is a common part of ECommerce solutions, usually for integration with ERPs and custom administrative interfaces where orders are to be exported, filtered, or edited. Orders often need to be loaded based on date of submission, their status, or on particular properties or meta field properties in the order or cart, including properties of its child objects (for example shipments or lineitems). EPiServer Commerce (R1-R3) offers an API to search for and retrieve orders. This blog will cover :&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * The capabilities and limitations of EPiServer Commerce order search   &lt;br /&gt;&amp;nbsp; * The API - classes and properties you need to understand   &lt;br /&gt;&amp;nbsp; * Code examples of different scenarios   &lt;br /&gt;&amp;nbsp; * An in-depth look at the search stored procedure that does most of the search work&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Quick And Easy Summary&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The simplest way to do order search in EPiServer Commerce using the API is to follow this pattern:&lt;/p&gt;
&lt;p&gt;Always begin your search code with:&lt;/p&gt;
&lt;p&gt;OrderSearchOptions searchOptions = new OrderSearchOptions();    &lt;br /&gt;searchOptions.CacheResults = false; //or true if the results may be recalled shortly     &lt;br /&gt;searchOptions.StartingRecord = 0; //or whatever you want to specify for paging purposes     &lt;br /&gt;searchOptions.RecordsToRetrieve = 10000;&amp;nbsp; //or whatever you want to specify for paging purposes     &lt;br /&gt;searchOptions.Namespace = &quot;Mediachase.Commerce.Orders&quot;;&lt;/p&gt;
&lt;p&gt;Then&lt;/p&gt;
&lt;p&gt;OrderSearchParameters parameters = new OrderSearchParameters();   &lt;br /&gt;searchOptions.Classes.Add(&quot;PurchaseOrder&quot;); //this returns only purchase orders, for shopping carts, specify ShoppingCart   &lt;br /&gt;parameters.SqlMetaWhereClause = &quot;&quot;;   &lt;br /&gt;parameters.SqlWhereClause = sqlQuery.ToString();   &lt;br /&gt;PurchaseOrder[] purchaseOrderCollection = OrderContext.Current.FindPurchaseOrders(parameters, searchOptions); //If you are searching for shopping carts, you would call the FindCarts method and return an array of Cart objects&lt;/p&gt;
&lt;p&gt;The sqlQuery above is of the form : “OrderGroupId IN (SELECT OrderGroupId FROM ….&amp;lt;your particular subquery filtering for the correct orders&amp;gt;)”. See the code examples below.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Capabilities/Limitations&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The order search is designed to return arrays of OrderGroup objects - ShoppingCart, PaymentPlan, PurchaseOrder. If you&#39;re not familiar with the order object and table hierarchy, go &lt;a href=&quot;http://sdk.episerver.com/commerce/1.1.2/default.htm&quot;&gt;here&lt;/a&gt;. Look under Order System/Order Object Model and Database Diagram.     &lt;br /&gt;Its not for returning shipments, lineitems, payment objects or any other objects/data in an OrderGroup, except in the context of the whole order. If you need to do an order search for those types of objects alone, the Order Search API won&#39;t help you. The API allows you to search for carts/orders/payment plans based on properties, including meta fields associated with any of the objects in an OrderGroup and any outside database tables you may want to use for filtering/joins.&lt;/p&gt;
&lt;p&gt;The search pattern implemented in the API is two parts : First get a list of OrderGroupIds as search results using the stored procedure ecf_OrderSearch and insert those values into a database table – OrderSearchResults (with a GUID that separates it from other search results). This is the search phase, which is based on the criteria you pass in to the search. Next, the OrderGroupIds are loaded from OrderSearchResults and the corresponding array of ShoppingCart/PurchaseOrder/PaymentPlan is loaded and returned.&lt;/p&gt;
&lt;p&gt;One other noteworthy limitation is that sorting can only be done based on OrderGroup table fields.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Primary Classes / Properties&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mediachase.Commerce.Orders.Search.OrderSearchOptions&lt;/strong&gt; &lt;br /&gt;This class allows you to specify the number of records to be returned, whether the results are to be cached, the type of OrderGroup data to be returned (e.g. ShoppingCart/PurchaseOrder/PaymentPlan), starting record number (for paging).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Properties      &lt;br /&gt;&lt;/strong&gt;CacheResults     &lt;br /&gt;Boolean. Set to true if the results may need to be re-retrieved soon.&lt;/p&gt;
&lt;p&gt;RecordsToRetrieve    &lt;br /&gt;StartingRecord     &lt;br /&gt;Integer. You can use these two fields to do paging on the search results&lt;/p&gt;
&lt;p&gt;Classes&lt;/p&gt;
&lt;p&gt;StringCollection. This allows you to specify which order metadata object you want to create a subquery on. For example, if you wanted to find all shipments where a shipment meta field value is a particular value, you could specify &quot;ShipmentEx&quot; for the classes property. This is a string collection but I&#39;d be hard pressed to find an example where more than one class needs to be specified (of course, someone will now come forward to point out a valuable use for multiple-class queries - to which I say &quot;bring it on&quot;). Here are all of the classes you could specify (with the corresponding database table that will be queried).&lt;/p&gt;
&lt;p&gt;CashCardPayment (OrderFormPayment_CashCard)    &lt;br /&gt;CreditCardPayment (OrderFormPayment_CreditCard)     &lt;br /&gt;ExchangePayment (OrderFormPayment_Exchange)     &lt;br /&gt;GiftCardPayment (OrderFormPayment_GiftCard)     &lt;br /&gt;InvoicePayment (OrderFormPayment_Invoice)     &lt;br /&gt;LineItemEx (LineItemEx)     &lt;br /&gt;OrderFormEx (OrderFormEx)     &lt;br /&gt;OrderGroupAddressEx (OrderGroupAddressEx)     &lt;br /&gt;OtherPayment (OrderFormPayment_Other)     &lt;br /&gt;PaymentPlan (OrderGroup_PaymentPlan)     &lt;br /&gt;PurchaseOrder (OrderGroup_PurchaseOrder)     &lt;br /&gt;ShipmentEx (ShipmentEx)     &lt;br /&gt;ShoppingCart (OrderGroup_ShoppingCart)&lt;/p&gt;
&lt;p&gt;Note that you do need to specify at least one class here, even if you don’t specify a SQLMetaWhereClause in the parameters object (below). If you don’t specify SQLMetaWhereClause but do specify an OrderGroup (“PurchaseOrder”, “ShoppingCart”, “PaymentPlan”), it will filter the results for that ordergroup type only.&lt;/p&gt;
&lt;p&gt;Namespace    &lt;br /&gt;For order searches, this should always be Mediachase.Commerce.Orders&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mediachase.Commerce.Orders.Search.OrderSearchParameters&lt;/strong&gt; &lt;br /&gt;This class allows you to specify the where clauses necessary to narrow your order search.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Properties &lt;/strong&gt; &lt;br /&gt;OrderByClause     &lt;br /&gt;This clause can only use ordergroup table fields. If this field isn’t set, OrderGroupId is used.&lt;/p&gt;
&lt;p&gt;SqlMetaWhereClause    &lt;br /&gt;This field matches up with the OrderSearchOptions.Classes field to form a subquery based on an OrderGroup metaclass. This clause looks like this:     &lt;br /&gt;&quot;META.CardType = &#39;Visa&#39;&quot; (where the option classes collection contains &quot;CreditCardPayment&quot;). That returns all orders/carts/payment plans with visa payments. META is the SQL alias for the table associated with the class you specify (see options Classes property above).&lt;/p&gt;
&lt;p&gt;SqlWhereClause    &lt;br /&gt;This is a SQL WHERE clause that you can use to specify additional criteria in your order search. This where clause is used to form a subquery in the search stored procedure (see the stored procedure breakdown below). Note that using the META keyword here will be referring to a different alias than using it in the SqlMetaWhereClause. In the SqlMetaWhereClause, it&#39;s an alias for the table associated with the class specified in the options object. In the SQLWhereClause, it refers to a table in memory that has two columns - Key (which is the OrderGroupId) and Rank, which is part of the meta where clause subquery - you probably won&#39;t want to use META here. The only table referenced directly in the SQL where clause is the OrderGroup table. So you could have a clause like : “OrderGroupId IN (SELECT OrdergroupId FROM Shipment WHERE NOT ShipmentTrackingNumber IS NULL)”. Note that you can’t add an “ORDER BY” clause here as its part of a subquery (see stored procedure description below).&lt;/p&gt;
&lt;p&gt;AdvancedFreeTextSearchPhrase    &lt;br /&gt;FreeTextSearchPhrase     &lt;br /&gt;This doesn’t work. These fields should be left blank.&lt;/p&gt;
&lt;p&gt;These fields are NOT USED for order search    &lt;br /&gt;JoinSourceTable     &lt;br /&gt;JoinSourceTableKey     &lt;br /&gt;JoinTargetQuery     &lt;br /&gt;JoinTargetTableKey     &lt;br /&gt;JoinType&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mediachase.Commerce.Orders.OrderContext &lt;/strong&gt; &lt;br /&gt;This class is a singleton with several methods to find orders:&lt;/p&gt;
&lt;p&gt;FindActiveOrders() To retrieve all PurchaseOrders where the OrderGroup.Status is either InProgress or PartiallyShipped.    &lt;br /&gt;FindCarts() allows you to find shopping carts based on parameters and options specified with the two other classes.     &lt;br /&gt;FindPaymentPlans() Same as FindCarts except with PaymentPlans     &lt;br /&gt;FindPurchaseOrders() Same as FindCarts except with PurchaseOrders     &lt;br /&gt;FindPurchaseOrdersByStatus() allows you to retrieve orders with particular order statuses&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Code Examples&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For all of the code examples, the following code is common and first defined:&lt;/p&gt;
&lt;p&gt;OrderSearchOptions searchOptions = new OrderSearchOptions();    &lt;br /&gt;searchOptions.CacheResults = false;     &lt;br /&gt;searchOptions.StartingRecord = 0;     &lt;br /&gt;searchOptions.RecordsToRetrieve = 10000;     &lt;br /&gt;searchOptions.Namespace = &quot;Mediachase.Commerce.Orders&quot;;&lt;/p&gt;
&lt;p&gt;The following examples are the code that follows the above code.&lt;/p&gt;
&lt;p&gt;1. Here’s an example of retrieving all purchase orders with a tracking number that is like a particular pattern and where a tracking number (a meta field) has been assigned to at least one of the shipments in the purchase order:&lt;/p&gt;
&lt;p&gt;OrderSearchParameters parameters = new OrderSearchParameters();    &lt;br /&gt;searchOptions.Classes.Add(&quot;PurchaseOrder&quot;);     &lt;br /&gt;parameters.SqlMetaWhereClause = &quot;META.TrackingNumber LIKE &#39;%PO%&#39;&quot;;     &lt;br /&gt;parameters.SqlWhereClause = &quot;OrderGroupId IN (SELECT OrdergroupId FROM Shipment WHERE NOT ShipmentTrackingNumber IS NULL)&quot;;&lt;/p&gt;
&lt;p&gt;PurchaseOrder[] purchaseOrderCollection = OrderContext.Current.FindPurchaseOrders(parameters, searchOptions);&lt;/p&gt;
&lt;p&gt;2. Here’s an example of retrieving all purchase orders that contain line items that have an RMANumber (a meta field) associated with them:&lt;/p&gt;
&lt;p&gt;OrderSearchParameters parameters = new OrderSearchParameters();    &lt;br /&gt;searchOptions.Classes.Add(&quot;LineItemEx&quot;);     &lt;br /&gt;parameters.SqlMetaWhereClause = &quot;NOT META.RMANumber IS NULL&quot;;     &lt;br /&gt;parameters.SqlWhereClause = &quot;OrderGroupId IN (Select ObjectId FROM OrderGroup_PurchaseOrder)&quot;;&lt;/p&gt;
&lt;p&gt;PurchaseOrder[] purchaseOrderCollection = OrderContext.Current.FindPurchaseOrders(parameters, searchOptions);&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTICE&lt;/strong&gt;: the SqlWhereClause is used to ensure that only purchase orders are returned. If you don’t do this, you may get carts (or payment plans, if they exist in your system) also in the dataset and you will get errors like :&lt;/p&gt;
&lt;h4&gt;&lt;em&gt;Index 0 is either negative or above rows count.&lt;/em&gt;&lt;/h4&gt;
&lt;p&gt;As the metadata object load is getting confused by the different meta fields in a purchase order vs a cart. In any cases where you’re querying orders using the API and you’re not specifying which OrderGroup object as the meta class you are seeking, you need to add this where filter in the SQLWhereClause. Of course, you can make a compound where clause including this filter…&lt;/p&gt;
&lt;p&gt;3. Here’s an example of retrieving all shopping carts where lineitems have an expiration date (a meta field) greater than a particular value:&lt;/p&gt;
&lt;p&gt;OrderSearchParameters parameters = new OrderSearchParameters();    &lt;br /&gt;searchOptions.Classes.Add(&quot;LineItemEx&quot;);     &lt;br /&gt;parameters.SqlMetaWhereClause = &quot;META.ExpirationDate &amp;gt; &#39;1/1/2012&#39;&quot;;     &lt;br /&gt;parameters.SqlWhereClause = &quot;OrderGroupId IN (Select ObjectId FROM OrderGroup_ShoppingCart)&quot;;     &lt;br /&gt;Cart[] cartCollection = OrderContext.Current.FindCarts(parameters, searchOptions);&lt;/p&gt;
&lt;p&gt;4. Here’s an example where all purchase orders are retrieved that have shipments with a particular a shipping status (a meta field) :&lt;/p&gt;
&lt;p&gt;OrderSearchParameters parameters = new OrderSearchParameters();    &lt;br /&gt;searchOptions.Classes.Add(&quot;ShipmentEx&quot;);     &lt;br /&gt;parameters.SqlMetaWhereClause = &quot;NOT META.PrevStatus IS NULL&quot;;     &lt;br /&gt;parameters.SqlWhereClause = &quot;OrderGroupId IN (Select ObjectId FROM OrderGroup_PurchaseOrder)&quot;;&lt;/p&gt;
&lt;p&gt;PurchaseOrder[] purchaseOrderCollection = OrderContext.Current.FindPurchaseOrders(parameters, searchOptions);&lt;/p&gt;
&lt;p&gt;5.Here’s one with a more complex SQL where clause to return all orders that contain SKUs whose name contains a substring by using a join with the CatalogEntry table. Notice that I don’t have a SqlMetaWhereClause but do specify a PurchaseOrder class.&lt;/p&gt;
&lt;p&gt;You can add any subquery here, allowing the most flexibility.&lt;/p&gt;
&lt;p&gt;StringBuilder sqlQuery = new StringBuilder();    &lt;br /&gt;sqlQuery.Append(&quot;OrderGroupId IN (SELECT OrderGroupId From LineItem li &quot;);     &lt;br /&gt;sqlQuery.Append(&quot;INNER JOIN CatalogEntry en &quot;);     &lt;br /&gt;sqlQuery.Append(&quot;ON li.CatalogEntryId = en.Code &quot;);     &lt;br /&gt;sqlQuery.Append(&quot;WHERE en.Name LIKE &#39;%Wine%&#39;)&quot;);&lt;/p&gt;
&lt;p&gt;OrderSearchParameters parameters = new OrderSearchParameters();    &lt;br /&gt;searchOptions.Classes.Add(&quot;PurchaseOrder&quot;);     &lt;br /&gt;parameters.SqlMetaWhereClause = &quot;&quot;;     &lt;br /&gt;parameters.SqlWhereClause = sqlQuery.ToString();     &lt;br /&gt;PurchaseOrder[] purchaseOrderCollection = OrderContext.Current.FindPurchaseOrders(parameters, searchOptions);&lt;/p&gt;
&lt;p&gt;6. Finally, here’s an example where several order meta data tables are joined in the SQL where clause. This is a better, easier way to do more complex order searches with multiple order meta classes (rather than using the options Classes collection for filtering order meta classes). Notice that the query below appears to have the potential to return duplicate ordergroupids as an order can have multiple shipments or lineitems. However, the order search stored procedure ensures that only distinct order ids are returned.&lt;/p&gt;
&lt;p&gt;StringBuilder sqlQuery = new StringBuilder();    &lt;br /&gt;sqlQuery.Append(&quot;OrderGroupId IN (SELECT li.OrderGroupId From LineItem li &quot;);     &lt;br /&gt;sqlQuery.Append(&quot;INNER JOIN LineItemEx ex &quot;);     &lt;br /&gt;sqlQuery.Append(&quot;ON li.LineItemId = ex.ObjectId &quot;);     &lt;br /&gt;sqlQuery.Append(&quot;INNER JOIN Shipment sh &quot;);     &lt;br /&gt;sqlQuery.Append(&quot;ON li.OrderGroupId = sh.OrderGroupId &quot;);     &lt;br /&gt;sqlQuery.Append(&quot;INNER JOIN ShipmentEx shex &quot;);     &lt;br /&gt;sqlQuery.Append(&quot;ON sh.ShipmentId = shex.ObjectId &quot;);     &lt;br /&gt;sqlQuery.Append(&quot;WHERE ex.ExpirationDate &amp;gt; &#39;1/1/2011&#39;&quot;);     &lt;br /&gt;sqlQuery.Append(&quot;AND NOT shex.PrevStatus IS NULL)&quot;);&lt;/p&gt;
&lt;p&gt;OrderSearchParameters parameters = new OrderSearchParameters();    &lt;br /&gt;searchOptions.Classes.Add(&quot;PurchaseOrder&quot;);     &lt;br /&gt;parameters.SqlMetaWhereClause = &quot;&quot;;     &lt;br /&gt;parameters.SqlWhereClause = sqlQuery.ToString();     &lt;br /&gt;PurchaseOrder[] purchaseOrderCollection = OrderContext.Current.FindPurchaseOrders(parameters, searchOptions);&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Some general tips when you’re writing your queries and adding them to your code:&lt;/p&gt;
&lt;p&gt;* Always test them in SQL Management Studio first&lt;/p&gt;
&lt;p&gt;* Remember the closing “)” for your SqlWhereClause property value.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;strong&gt;Stored procedure breakdown&lt;/strong&gt; &lt;br /&gt;The first part of the order search is to perform a dynamic search for orders based on the OrderSearchParameters and OrderSearchOption properties set in code.&lt;/p&gt;
&lt;p&gt;Here&#39;s the final query that&#39;s built based on the settings you specify. This is assuming that the AdvancedFreeTextSearchPhrase and FreeTextSearchPhrase properties are left blank.    &lt;br /&gt;The stored procedure which uses these properties to create a list of order ids is ecf_OrderSearch.&lt;/p&gt;
&lt;p&gt;declare @Page_temp table (TotalRecords int, OrderGroupId int);    &lt;br /&gt;with OrderedResults as     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (SELECT count([OrderGroup].OrderGroupId)     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; OVER() TotalRecords, [OrderGroup].OrderGroupId, Rank, ROW_NUMBER()     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; OVER(ORDER BY &amp;lt;&lt;strong&gt;OrderBy property&amp;gt;&lt;/strong&gt;) RowNumber&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FROM [OrderGroup] OrderGroup     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; INNER JOIN     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (select distinct U.[KEY], U.Rank from     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (select 100 as &#39;Rank&#39;, META.ObjectId as &#39;Key&#39;, * from &amp;lt;&lt;strong&gt;database table associated with the order metaclass in the options Classes property&amp;gt;&lt;/strong&gt; META     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; WHERE &amp;lt;&lt;strong&gt;SQLMetaWhereClause&amp;gt;&lt;/strong&gt;) U) META     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ON OrderGroup.[OrderGroupId] = META.[KEY]&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; WHERE ApplicationId = &#39;[Application ID]&#39; AND     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (&amp;lt;&lt;strong&gt;SQLWhereClause&amp;gt;&lt;/strong&gt;))     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; INSERT INTO @Page_temp (TotalRecords, OrderGroupId)     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SELECT top(&amp;lt;&lt;strong&gt;RecordsToRetrieve&lt;/strong&gt;&amp;gt;) TotalRecords, OrderGroupId FROM OrderedResults     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; WHERE RowNumber &amp;gt; &amp;lt;&lt;strong&gt;StartingRecord&lt;/strong&gt;&amp;gt;;;     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; select @RecordCount = TotalRecords from @Page_temp;     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; INSERT INTO OrderSearchResults (SearchSetId, OrderGroupId)     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SELECT [SearchSetId], OrderGroupId from @Page_temp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;I’ve added formatting to make it a bit easier to read. Some things to note :&lt;/p&gt;
&lt;p&gt;* The query for meta data is built using the class you specify and the SQLMetaWhereClause you specify (already mentioned above). The META where clause *inside* the SQLMetaWhereClause is different from the META alias available to the SQLWhereClause.&lt;/p&gt;
&lt;p&gt;* If no SQLWhereClause is specified, the query executes without the AND [SQLWhereClause] portion. The same goes for SQLMetaWhereClause.&lt;/p&gt;
&lt;p&gt;* Don’t worry about Application ID. This is managed internally by EPiServer Commerce.&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/Shannon-Gray/Dates/2012/12/EPiServer-Commerce-Order-Search-Made-Easy/</guid>            <pubDate>Thu, 20 Dec 2012 23:22:00 GMT</pubDate>           <category>Blog post</category></item><item> <title>Generating Typed Business Foundation Classes In EPiCommerce</title>            <link>https://world.optimizely.com/blogs/Shannon-Gray/Dates/2012/8/Generating-Typed-Business-Foundation-Classes-In-EPiCommerce/</link>            <description>&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Business Foundation is a pretty powerful tool... and you&#39;ll probably like it more if you make use of what I provide here. We&#39;ll be covering code generation and templates to make access and modification of business foundation data much cleaner and clearer.&lt;/p&gt;  &lt;p&gt;Business Foundation is an Object Relation Modeling tool built right into EPiCommerce that allows you to very easily store custom data that doesn&#39;t fit into the EPiCommerce data model. I&#39;ve found Business Foundation useful in most implementations because there&#39;s almost always custom data that needs to be stored about customer pricing, gift cards, authentication SSO tickets, and a myriad of other things. &lt;/p&gt;  &lt;p&gt;For more introduction to using Business Foundation, take a look at this blog from Cecilia von Wachenfelt:    &lt;br /&gt;&lt;a href=&quot;http://world.episerver.com/Blogs/Cecilia-von-Wachenfeldt/Dates/2011/7/An-example-using-Business-Foundation-in-EPiServer-Commerce/&quot;&gt;http://world.episerver.com/Blogs/Cecilia-von-Wachenfeldt/Dates/2011/7/An-example-using-Business-Foundation-in-EPiServer-Commerce/&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;For a more in-depth look at the Business Foundation architecture and API, look here:    &lt;br /&gt;&lt;a href=&quot;http://sdk.episerver.com/commerce/1.1/Content/Developers%20Guide/Architecture/BusinessFoundation/Business%20Foundation.htm&quot;&gt;http://sdk.episerver.com/commerce/1.1/Content/Developers%20Guide/Architecture/BusinessFoundation/Business%20Foundation.htm&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;The Problem&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;There are only a few problems I have with using Business Foundation. They&#39;re problems that are solvable but the solutions were always manual. Here are the problems I&#39;ve had with it:&lt;/p&gt;  &lt;p&gt;* Business Foundation entities are not typed. This means there were string names for fields everywhere in the code, which is quite error prone and messy. This can be addressed using string constants to make sure they&#39;re only defined once.    &lt;br /&gt;* Caching is not built into Business Foundation, unlike the catalog, customer, and order subsystems.     &lt;br /&gt;* The interface to retrieve Business Foundation entities is too generic. Its powerful by being generic but the code readability is low because of it.     &lt;br /&gt;* It provides most of what&#39;s needed for paging but its missing a returned record count. When paging is setup, you need to know a) what the first record is needed on a page, b) the number of records to be returned, and c) the total number of records available to paging control (so the number of pages can be calculated). Right now, Business Foundation only allows you to input the first two parameters but doesn&#39;t give you a count of the total number of records matching the query.&lt;/p&gt;  &lt;p&gt;Here is an example snapshot that shows you what it looks like using the existing Business Foundation objects: &lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;/link/5cdd7dbf96d84318bfd433c4e58ba88e.png&quot;&gt;&lt;img style=&quot;background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px&quot; title=&quot;OldWay&quot; border=&quot;0&quot; alt=&quot;OldWay&quot; src=&quot;/link/3318dc5fed0341e2ac785536c8fa0bd3.png&quot; width=&quot;244&quot; height=&quot;136&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;A Solution&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;McCodeGen.exe is a free tool (I didn&#39;t write it) for business foundation code generation that will help solve this (see download link below). I&#39;ve also provided some templates that can be used by McCodeGen.exe to specifically address these shortcomings. By following the directions below, you can create several typed business foundation classes. &lt;/p&gt;  &lt;p&gt;One template is a simple typed Business Foundation class that will generate a class file for an existing Business Foundation class. It inherits from EntityObject and simply exposes the fields for an EntityObject as typed fields. The next two templates can be used to create context classes which provide an API to retrieve, update, create, and delete these typed Business Foundation objects. One of the context class templates provides access to the Business Manager methods used to retrieve EntityObject but returns the typed objects instead. The other context class template has this functionality plus caching and full paging support. The latter context class allows you to configure the cache timeout.&lt;/p&gt;  &lt;p&gt;The typed Business Foundation class template is called &amp;quot;EntityObjectSimple.aspx&amp;quot;. The non-cached typed Business Foundation context class template is &amp;quot;EntityObjectAccessTemplateNoCaching.aspx&amp;quot;. The cached typed Business Foundation context class template is &amp;quot;EntityObjectAccessTemplate.aspx&amp;quot;. All of these files are located in the Templates folder of the files in the download link below.&lt;/p&gt;  &lt;p&gt;Here&#39;s an example of what the above code would look like using generated classes: &lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;/link/2d1be13bc2e047bca9d431d5858c480b.png&quot;&gt;&lt;img style=&quot;background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px&quot; title=&quot;NewWay&quot; border=&quot;0&quot; alt=&quot;NewWay&quot; src=&quot;/link/7f2d739a870f434d82fa93f155fab884.png&quot; width=&quot;244&quot; height=&quot;148&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Much better right?&lt;/p&gt;  &lt;p&gt;There are a few caveats/points to be made about the classes created using these templates:    &lt;br /&gt;* Classes generated by the class template will have a property called ExtendedProperties to provide access to any fields that have been added to the business foundation entity definition but not defined in the class (for example, because the class wasn&#39;t updated after a field was added).     &lt;br /&gt;* Classes generated by the cached context class provides overloads to either a)use a member Boolean property value to determine whether to use the cache or not or b) override the member Boolean indicator with your own specific caching indicator. So you can set a default value for whether records will be cached or retrieved from the cache AND you can override it when needed.     &lt;br /&gt;* The cached context class template explicitly defines the cache timeout period. Its very easy to change the template to your desired timeout or use config settings to set them. There are lots of possible enhancements/changes you may need to make here depending on your implementation.     &lt;br /&gt;* Both context classes are singletons.     &lt;br /&gt;* The record count takes the FilterElements to build a dynamic SQL query and return a count of the number of records matching the List(/Search) conditions. I&#39;m sure there are more elegant solutions and I&#39;m happy to repost (and give credit) if someone wants to improve that... or for that matter, any part of these templates.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Directions For Implementing&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;1. Unzip download from here: &lt;a href=&quot;http://world.episerver.com/Code/Shannon-Gray/Code-for-Creating-Typed-Business-Foundation-Classes/&quot;&gt;Download Link&lt;/a&gt;. Add the following .dlls to the bin directory.     &lt;br /&gt;* Mediachase.BusinessFoundation.Data.dll     &lt;br /&gt;* Mediachase.BusinessFoundation.Data.XmlSerializers.dll     &lt;br /&gt;* Mediachase.BusinessFoundation.ObjectModel.dll     &lt;br /&gt;* Mediachase.CodeGen.dll     &lt;br /&gt;* Mediachase.CodeGen.VisualStudio.dll     &lt;br /&gt;* Mediachase.Ibn.Core.dll     &lt;br /&gt;* Mediachase.Ibn.Core.XmlSerializers.dll     &lt;br /&gt;* Mediachase.Ibn.Data.dll     &lt;br /&gt;* Mediachase.Ibn.Data.Services.dll     &lt;br /&gt;* Mediachase.Ibn.Data.XmlSerializers.dll     &lt;br /&gt;* Mediachase.Ibn.ObjectModel.dll&lt;/p&gt;  &lt;p&gt;2. Copy one of the .mcgen files into the root folder (doesn&#39;t matter which one). Rename the .mcgen file so that its clear what Business Foundation class its associated with and the purpose of the class.    &lt;br /&gt;3. Open the newly-copied version of the .mcgen file in the root folder. Set the connectionString element value to the connection string for the EPiCommerce database containing the business foundation class definition. Set the MetaClass element value to the name of the Business Foundation class you are typing. In the mcgen element, set the template for the class you want to use, for example &amp;quot;Templates\EntityObjectAccessTemplate.aspx&amp;quot;; see the descriptions of the templates above. In the params definition, set the namespace param value to the namespace you&#39;d like the class to contain, for example &amp;quot;EPiServer.Training.BusinessObjects&amp;quot;.     &lt;br /&gt;4. Open a commandline to the root folder. Run the following command :     &lt;br /&gt;mccodegen -mcgen:&amp;lt;name of .mcgen file created in the root folder in step 2. Include the .mcgen extension&amp;gt; -out:&amp;lt;name of class file to be generated with .cs at the end&amp;gt;&lt;/p&gt;  &lt;p&gt;For example: mccodegen -mcgen:GiftCard.mcgen -out:GiftCardAccess.cs&lt;/p&gt;  &lt;p&gt;A new class will be created based on the template you specified. You&#39;ll need to use the typed class template to generate the typed Business Foundation class; use one of the context class templates to generate the context class to access the typed Business Foundation class.&lt;/p&gt;  &lt;p&gt;5. For each typed class or context class you want to create, iterate through steps 2-4.&lt;/p&gt;  &lt;p&gt;For further study, there are ways to build the McCodeGen tool into your Visual Studio environment so that you can easily update you typed Business Foundation classes as described here:    &lt;br /&gt;&lt;a href=&quot;http://sdk.episerver.com/commerce/1.1/Content/Developers%20Guide/Architecture/BusinessFoundation/Working%20with%20Entity%20Objects.htm&quot;&gt;http://sdk.episerver.com/commerce/1.1/Content/Developers%20Guide/Architecture/BusinessFoundation/Working%20with%20Entity%20Objects.htm&lt;/a&gt;     &lt;br /&gt;I&#39;ve included files necessary for this in the Visual Studio folder.&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/Shannon-Gray/Dates/2012/8/Generating-Typed-Business-Foundation-Classes-In-EPiCommerce/</guid>            <pubDate>Tue, 21 Aug 2012 04:39:13 GMT</pubDate>           <category>Blog post</category></item><item> <title>Creating tiered pricing for organizations and visitor groups</title>            <link>https://world.optimizely.com/blogs/Shannon-Gray/Dates/2012/7/Creating-tiered-pricing-for-organizations-and-visitor-groups/</link>            <description>&lt;p&gt;This is the third part in a 3-part series about understanding and extending the EPiCommerce tiered pricing engine. This is a demonstration of how you&#39;d extend tiered pricing to organization-specific and visitor-group-specific pricing. The first and second parts can be found here:&lt;/p&gt;  &lt;p&gt;Part 1: &lt;a href=&quot;http://world.episerver.com/Blogs/Shannon-Gray/Dates/2012/7/Extending-EPiCommerce-Tiered-Pricing-or-Whats-your-VisitorGroup-Price-For-That-SKU/&quot;&gt;&lt;font size=&quot;2&quot;&gt;Intro to Tiered Pricing&lt;/font&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Part 2: &lt;a href=&quot;http://world.episerver.com/Blogs/Shannon-Gray/Dates/2012/7/How-Do-I-Implement-Custom-Sale-Types-For-Visitor-Groups-Or-Users-Associated-With-An-Organization/&quot;&gt;&lt;font size=&quot;2&quot;&gt;How to extend Tiered Pricing&lt;/font&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Organization-specific pricing&lt;/strong&gt;     &lt;br /&gt;To add pricing specific to organizations, follow these steps:&lt;/p&gt;  &lt;p&gt;1. Add a new sale type in the ecf.catalog.config file, the SalePriceTypes section:&lt;/p&gt;  &lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;    &amp;lt;add key=&lt;span class=&quot;str&quot;&gt;&amp;quot;CustomPricing.Organization&amp;quot;&lt;/span&gt; &lt;span class=&quot;kwrd&quot;&gt;value&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&amp;quot;3&amp;quot;&lt;/span&gt; description=&lt;span class=&quot;str&quot;&gt;&amp;quot;Organization&amp;quot;&lt;/span&gt;&amp;gt;&amp;lt;/add&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;style type=&quot;text/css&quot;&gt;






.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, &quot;Courier New&quot;, courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }&lt;/style&gt;

&lt;br /&gt;The key is the Session variable name. There is no required convention in the name except that it of course be unique. The value must be a number, consecutive to the other tiered pricing types. The description is the name that will be displayed in Commerce Manager as a new sale type option. 

&lt;p&gt;&amp;#160;&lt;/p&gt;

&lt;p&gt;Once you’ve saved this change, when you edit/add tiered pricing, you&#39;ll see a new option for Organization in the Sale Type dropdown and be able to add tiered pricing for an organization. If you specify an organization name for the sale code, you have now set a tiered price for customers associated with that organization.&lt;/p&gt;

&lt;p&gt;&amp;#160;&lt;/p&gt;

&lt;p&gt;2. Add code somewhere in your front end site to set the session variable like this:&lt;/p&gt;

&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;            &lt;span class=&quot;rem&quot;&gt;//map the customer organization to a session variable for pricing mapping&lt;/span&gt;
            &lt;span class=&quot;kwrd&quot;&gt;if&lt;/span&gt; (Session[&lt;span class=&quot;str&quot;&gt;&amp;quot;CustomPricing.Organization&amp;quot;&lt;/span&gt;] == &lt;span class=&quot;kwrd&quot;&gt;null&lt;/span&gt; &amp;amp;&amp;amp;
                CustomerContext.Current.CurrentContact != &lt;span class=&quot;kwrd&quot;&gt;null&lt;/span&gt; &amp;amp;&amp;amp;
                CustomerContext.Current.CurrentContact.ContactOrganization != &lt;span class=&quot;kwrd&quot;&gt;null&lt;/span&gt;)
            {
                &lt;span class=&quot;kwrd&quot;&gt;string&lt;/span&gt; orgName = 
                CustomerContext.Current.CurrentContact.ContactOrganization.Name;
                Session[&lt;span class=&quot;str&quot;&gt;&amp;quot;CustomPricing.Organization&amp;quot;&lt;/span&gt;] = orgName;
            }&lt;/code&gt;&lt;/pre&gt;
&lt;style type=&quot;text/css&quot;&gt;






.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, &quot;Courier New&quot;, courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }&lt;/style&gt;

&lt;p&gt;You may want to add code so that, when a user logs out, the session variable is set to null. Now, if you set a tiered price for an organization for a particular SKU (and its the lowest possible price for the user), it will be the displayed price for that SKU for users associated with that organization when they authenticate to the site. An obvious pre-requisite is you’ll need to have organizations created in the customer subsystem in Commerce Manager and users associated with them to use this tiered pricing.&lt;/p&gt;

&lt;p&gt;&amp;#160;&lt;/p&gt;

&lt;p&gt;3. If you want to display a dropdown with a list of organizations when you are adding the tiered price for an organization in Commerce Manager, there&#39;s one more step. You need to create a control that you register with the system. 
  &lt;br /&gt;&amp;#160; a. Create a new empty web project. 

  &lt;br /&gt;&amp;#160; b. Add a new web control&amp;#160; that implements the IAdminTabControl and IAdminContextControl interfaces. See the example implementation below. 

  &lt;br /&gt;&amp;#160; The IAdminContextControl interface has one method, LoadContext(), which is called by Commerce Manager to provide the sale code to display what value has already been saved, if any. 

  &lt;br /&gt;&amp;#160; The IAdminTabControl interface has one method, SaveChanges(), which is called by Commerce Manager to allow the sale code to be updated to the new value specified by the user or other business logic. 

  &lt;br /&gt;&amp;#160; c. You&#39;ll need to add references to the project. At a minimum, you&#39;ll need to add references to the EPiServer.dll and Mediachase.Commerce.dll. 

  &lt;br /&gt;&amp;#160; d. Copy the project dll for the new web project into the commerce manager project bin folder. 

  &lt;br /&gt;&amp;#160; e. Copy the web control ascx file into the Apps/Catalog/Modules folder of the Shared folders for your site. 

  &lt;br /&gt;&amp;#160; f. In the ecf.catalog.config, amend the new key from step 1 above with something like : 

  &lt;br /&gt;&amp;#160;&amp;#160; controlUrl=&amp;quot;~/Apps/Catalog/Modules/OrganizationPicker.ascx&amp;quot; to map to the new control’s name and location.&lt;/p&gt;

&lt;p&gt;&amp;#160;&lt;/p&gt;

&lt;p&gt;Now you can set tiered pricing for organizations and the organizations are conveniently populated in a dropdown list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;/strong&gt;&amp;#160;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VisitorGroup&lt;/strong&gt; 

  &lt;br /&gt;This is a little different from the organization list. The reason is that a user can be associated with multiple visitor groups and the pricing engine maps one session variable as one tiered pricing target. So this implementation is designed to create a single sale type for each visitor group that you&#39;re providing pricing for. Here are the steps to add tiered pricing for a visitor group: 

  &lt;br /&gt;&lt;/p&gt;

&lt;p&gt;1. Add a new sale type in the ecf.catalog.config file like this: 
  &lt;br /&gt;&lt;/p&gt;

&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;    &amp;lt;add key=&lt;span class=&quot;str&quot;&gt;&amp;quot;CustomPricing.VisitorGroup.special&amp;quot;&lt;/span&gt; &lt;span class=&quot;kwrd&quot;&gt;value&lt;/span&gt;=&lt;span class=&quot;str&quot;&gt;&amp;quot;4&amp;quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt; 
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;    description=&lt;span class=&quot;str&quot;&gt;&amp;quot;Visitor Group - Special&amp;quot;&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;    controlUrl=&lt;span class=&quot;str&quot;&gt;&amp;quot;~/Apps/Catalog/Modules/VisitorGroupControl.ascx&amp;quot;&lt;/span&gt;&amp;gt;&amp;lt;/add&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;style type=&quot;text/css&quot;&gt;






.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, &quot;Courier New&quot;, courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }&lt;/style&gt;

&lt;br /&gt;where “special” is the name of the visitor group we&#39;re targeting. The key and value again must be unique. The controlUrl points to another attached file below. 

&lt;br /&gt;

&lt;p&gt;2. Add code somewhere in your application to set the VisitorGroup session variable. It should execute with every page load as a user’s membership to a VisitorGroup can change during a session. The code could be written to go through the catalog config file and add all specified visitor group price types, in which case it could look something like this:&lt;/p&gt;

&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;            &lt;span class=&quot;kwrd&quot;&gt;string&lt;/span&gt; visitorGroup;
            &lt;span class=&quot;kwrd&quot;&gt;string&lt;/span&gt; visitorGroupPrefix = &lt;span class=&quot;str&quot;&gt;&amp;quot;CustomPricing.VisitorGroup.&amp;quot;&lt;/span&gt;;
            &lt;span class=&quot;kwrd&quot;&gt;string&lt;/span&gt; visitorGroupSessionVariable;
            EPiServer.Personalization.VisitorGroups.VisitorGroupHelper hlpr = &lt;span class=&quot;kwrd&quot;&gt;null&lt;/span&gt;;

            &lt;span class=&quot;rem&quot;&gt;//go through the sale price types from the ecf.catalog.config file&lt;/span&gt;
            Mediachase.Commerce.Catalog.SalePriceTypeCollection salesPriceTypes = 
            CatalogConfiguration.Instance.SalePriceTypes;

            &lt;span class=&quot;kwrd&quot;&gt;foreach&lt;/span&gt; (SalePriceTypeDefinition saleType &lt;span class=&quot;kwrd&quot;&gt;in&lt;/span&gt; salesPriceTypes)
            {
                &lt;span class=&quot;rem&quot;&gt;//if any sale types have a prefix of the custom pricing namespace&lt;/span&gt;
                &lt;span class=&quot;kwrd&quot;&gt;if&lt;/span&gt; (saleType.Key.StartsWith(visitorGroupPrefix))
                {
                    visitorGroupSessionVariable = saleType.Key;
                    &lt;span class=&quot;kwrd&quot;&gt;if&lt;/span&gt; (hlpr == &lt;span class=&quot;kwrd&quot;&gt;null&lt;/span&gt;)
                        hlpr = &lt;span class=&quot;kwrd&quot;&gt;new&lt;/span&gt; VisitorGroupHelper(); 
                    
                    visitorGroup = saleType.Key.Replace(visitorGroupPrefix, &lt;span class=&quot;str&quot;&gt;&amp;quot;&amp;quot;&lt;/span&gt;);

                    &lt;span class=&quot;rem&quot;&gt;//if the user is in the visitor group, add a session &lt;/span&gt;
                    &lt;span class=&quot;rem&quot;&gt;//variable for pricing mapping
                    &lt;span class=&quot;kwrd&quot;&gt;if&lt;/span&gt; (hlpr.IsPrincipalInGroup(PrincipalInfo.CurrentPrincipal, 
                        visitorGroup))
                        {
                        Session[visitorGroupSessionVariable] = &lt;span class=&quot;str&quot;&gt;&amp;quot;true&amp;quot;&lt;/span&gt;;
                        }
                    &lt;span class=&quot;kwrd&quot;&gt;else&lt;/span&gt;
                        Session.Remove(visitorGroupSessionVariable);
                }
            }&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;&amp;#160;&lt;/code&gt;&lt;/pre&gt;

In this case, the Sale Code is being set to &amp;quot;true&amp;quot;, an arbitrary value to map to the value set in the control in step 3.

&lt;p&gt;&amp;#160;&lt;/p&gt;

&lt;p&gt;3. Add a control the same way as outlined above for the organization tiered pricing control. See below for the demo control. The sale code is set to “true” always, since there’s no other specific information needed to map a visitor group to a tiered price. &lt;/p&gt;

&lt;p&gt;&amp;#160;&lt;/p&gt;

&lt;p&gt;There you go. You should now also be able to add tiered pricing for a visitor group. These are just a few examples of what you can do to expand the scope of tiered pricing. I&#39;m sure there are others with even better ways to implement this, particularly with the VisitorGroup classes - please share and I&#39;ll update the code with improvements. By the way, I have to credit the idea for the visitor group tiered pricing to a brainstorm with Chris Pope, &lt;a href=&quot;http://world.episerver.com/System/Users-and-profiles/Community-Profile-Card/Andreas%20Stjernstr%C3%B6m/&quot;&gt;Andreas Sjernstrom&lt;/a&gt;, and &lt;a href=&quot;http://world.episerver.com/System/Users-and-profiles/Community-Profile-Card/Fiur/&quot;&gt;Sofia Max&lt;/a&gt; - thanks!&lt;/p&gt;

&lt;p&gt;&amp;#160;&lt;/p&gt;

&lt;p&gt;On a final note, the pricing engine is getting updated with a provider model in the next version of EPiCommerce, making it easier to implement your custom pricing logic in EPiCommerce. I&#39;ll be blogging about that shortly after that is released.&lt;/p&gt;

&lt;p&gt;&amp;#160;&lt;/p&gt;

&lt;p&gt;Download the code mentioned above &lt;a href=&quot;http://world.episerver.com/Code/Shannon-Gray/Controls-for-Custom-Tiered-Pricing/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/Shannon-Gray/Dates/2012/7/Creating-tiered-pricing-for-organizations-and-visitor-groups/</guid>            <pubDate>Tue, 17 Jul 2012 00:24:20 GMT</pubDate>           <category>Blog post</category></item><item> <title>How Do I Implement Custom Sale Types For Visitor Groups Or Users Associated With An Organization?</title>            <link>https://world.optimizely.com/blogs/Shannon-Gray/Dates/2012/7/How-Do-I-Implement-Custom-Sale-Types-For-Visitor-Groups-Or-Users-Associated-With-An-Organization/</link>            <description>&lt;p&gt;This is Part 2 in a 3-part series on understanding and extending tiered pricing in EPiCommerce. In Part 1, we covered how tiered pricing works and hinted at how to extend it. In this post, we’ll go over the mechanics of implementing custom sale types so that your site can have more customized pricing.&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;How do you extend the EPiCommerce pricing engine with custom sale types? The pricing engine compares custom sale types and their associated sale codes implemented in Commerce manager with session variables set on your customer site. It does this to determine whether the current customer browsing the site matches the customer target information (sale code) for that custom sale type. This is done for each SKU that uses that custom sale type.&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;More specifically, custom sale type implementation requires three things :&lt;/p&gt;  &lt;p&gt;* A custom sale type in the ecf.catalog.config file&lt;/p&gt;  &lt;p&gt;* A tiered price manually or programmatically added for the SKU in the catalog / Commerce Manager&lt;/p&gt;  &lt;p&gt;* A session variable set in memory during a user&#39;s session on the site. &lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;Once the config file is updated, Commerce Manager will display the new sale type option to allow the addition/editing of tiered pricing for this new sale type in any SKU, bundle, or package. Next, manually or programmatically, you’ll need to add a tiered price for one or more skus which use this new sale type with the sale code that reflects the sale code the user must match to have access to this price. Somewhere in your site, you need to set a session variable with the same name as the custom sale type in the config file and the applicable sale code for the current user. &lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;An example of a sale type would be Organization; an example of an organization sale code for a particular customer would be “Acme Corporation”. In this example, a session variable called “Organization” would be added to the customer site session and the value of that session variable would be set to the name of the organization the current user is associated with – here, “Acme Corporation”. If a SKU has a tiered price for the sale type Organization where the sale code is Acme Corporation, and its lower than the other prices that are applicable for the currently-browsing-customer in this example, the Acme Corporation tiered price will be returned when calling the StoreHelper.GetSalePrice() method (which returns the EPiCommerce pricing engine price). The session variable doesn’t have to be present if it doesn’t apply to the current customer. &lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;As a bonus, EPiCommerce has a way to let you display your own user control in Commerce Manager when configuring the sale code property of your custom sale type. You can use this to display only valid values in a dropdown box, for example.&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;Here are some examples of how this could be used :    &lt;br /&gt;* You could have an organization sale type which allows you to specify a price for customers associated with an organization     &lt;br /&gt;* You could specify a sale type where a price can be setup for users associated with a particular visitor group     &lt;br /&gt;* Other properties of a customer, including information from sources outside of the EPiCommerce customer data model, could be used to create a custom sale type &lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/Shannon-Gray/Dates/2012/7/How-Do-I-Implement-Custom-Sale-Types-For-Visitor-Groups-Or-Users-Associated-With-An-Organization/</guid>            <pubDate>Wed, 11 Jul 2012 20:00:40 GMT</pubDate>           <category>Blog post</category></item><item> <title>Extending EPiCommerce Tiered Pricing or What&#39;s your VisitorGroup Price For That SKU?</title>            <link>https://world.optimizely.com/blogs/Shannon-Gray/Dates/2012/7/Extending-EPiCommerce-Tiered-Pricing-or-Whats-your-VisitorGroup-Price-For-That-SKU/</link>            <description>&lt;p&gt;This is part 1 of 3 blog posts created to give you some background on how tiered pricing works in EPiCommerce and how you can extend it to do cool things like have pricing for visitor groups or other pricing scenarios you need to handle.&lt;/p&gt;  &lt;p&gt;When you&#39;ve used or demo&#39;d the tiered pricing, you probably have used the dropdown for Sale Type and wondered &amp;quot;But how do I set pricing for customers in visitor groups or customers associated with organizations, or with a particular customer attribute other than price group?&amp;quot;. Or perhaps you&#39;ve thought &amp;quot;But what if I want to cover the [fill in your custom pricing scenario]?&amp;quot; &lt;/p&gt;  &lt;p&gt;The built-in pricing provider can be easily extended to cover these scenarios and still be editable in Commerce Manager. When tiered pricing is extended, you can make new options available in Commerce Manager to allow site administrators to easily create a tiered price on a stock keeping unit (SKU) that&#39;s more specific to their custom scenarios. Before we cover how to do that, it helps to better understand how tiered pricing works.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;The Technical Background on Tiered Pricing&lt;/strong&gt;     &lt;br /&gt;From the Commerce Manager view, when you&#39;re editing tiered pricing in Commerce Manager, you&#39;re editing the rows of data in the SalePrice database table that are associated with a SKU, package, or bundle. From the API view, those tiered pricing rows are available from an Entry object as an array of SalePrice objects or as rows in the SalePrice datatable in the CatalogEntryDto. You probably won&#39;t use those objects representing sale prices unless your doing a custom import of catalog data. But you use them indirectly whenever you call StoreHelper.GetSalePrice() or StoreHelper.GetDiscountPrice(). The GetSalePrice() method (which is called inside GetDiscountPrice()) is essentially the built-in pricing engine. It parses through the list price (aka display price) for an Entry and its associated SalePrice rows to find the lowest applicable price for a SKU given the customer (if they’re logged in), date, quantity being purchased, and customer target information.&lt;/p&gt;  &lt;p&gt;What is customer target information? This is information used to segment customers defined by the sale type and sale code. In the SalePrice datatable, these are the SaleType and SaleCode fields. The built-in segmentation are : Customer and Customer Price Group. Customer allows you to specify the username of a customer to provide a specific price for a SKU - mostly useful in B2B-type scenarios. The Customer Price Group is an attribute of the CustomerContact object that is configurable with the properties of users in the Customer subsystem; its marked as Customer Group when you&#39;re viewing/editing a customer in Commerce Manager. This allows you to specify the price of a SKU for customers which have the same price group specified. The default customer target is All Customers, which makes no distinctions based on customer target information (whether logged in or not).&lt;/p&gt;  &lt;p&gt;So where are these customer targets defined? They are defined in the Configs/ecf.catalog.config file, in the SalePriceTypes section. This means you can add your own sale types. But if you add your own sale types, how are those used by the system to map pricing to a customer? And what are examples that demonstrate the power of this capability?&lt;/p&gt;  &lt;p&gt;This is covered in parts 2 and 3, respectively…    &lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/Shannon-Gray/Dates/2012/7/Extending-EPiCommerce-Tiered-Pricing-or-Whats-your-VisitorGroup-Price-For-That-SKU/</guid>            <pubDate>Wed, 11 Jul 2012 04:38:13 GMT</pubDate>           <category>Blog post</category></item></channel>
</rss>