Blog posts by Son Do2022-10-08T15:27:42.0000000Z/blogs/Son-Do/Optimizely WorldAdding market to Optimize Commerce site url in .net core/blogs/Son-Do/Dates/2022/10/add-currebt-martket-to-site-url-in--net-core/2022-10-08T15:27:42.0000000Z<p>As we all know, <a href="/link/932ce3c1084944be9f4dd313cd2cdccd.aspx">CMS 12 and Commerce 14</a> a brought brand new evolution to the developer, with .net core, performance improvement and headless style. Beside the great announcement, the major version contains a lot of breaking changes which were highlight in the <a href="/link/897c60dca21a4658b9df07414adbdf7c.aspx">document</a>. However, we need to migrate one by one in each document section.</p>
<p>In the topic today, I'd like to dig a bit into the routing which was introduced in <strong>CMS 12</strong>.</p>
<p>I faced a challenge that is adding the market id to site url. The expectation is the site url contains language, market and the rest of content, it should be like <em>https://mycommercesite/en/sw/new-arrivals/</em> for the CMS content, where en is language code and sw (Sweden) is a market id. In addition, the Commerce products content url should looks like <em>https://mycommercesite/en/sw/<span style="text-decoration: line-through;">fashion/</span>mens/mens-sweatshirts/p-22471481/</em> , and I need to remove fashion - <em>the catalog name</em> as well since we used only 1 catalog. The requirement is great, clear enough and a challenge :) </p>
<p><img src="https://lh3.googleusercontent.com/pw/AL9nZEU6Me1oWRoe7Cm8X2fyjatjR-MLB9B3tXI6CLcBcovy5LU1CotL5ycZgRtwqljXdOAJjyfIjQ9aiOQwPmB3v-C0m4moJVDzVHUxR1cg9QNb4RIBNUP8ziVZvEG8GsjTDkm-6qDa2FVHGDKq19Dfbi_8Jg=w1742-h1023-no?authuser=0" width="1000" alt="Market routing in url" height="587" /></p>
<p>I found some blog post, forum post which relates to this request. The <span style="font-weight: 400;"><a href="https://marisks.net/2017/09/28/market-routing-in-episerver-commerce/">Māris' blog post</a> </span>could be mentioned as a solution which c<span style="font-weight: 400;">an be applied to CMS 11 and below, the post is great, but unfortunately, it cannot be applied to .Net core and CMS 12 site.</span></p>
<p>So I dig into a couple of documents for routing in CMS 12</p>
<ul>
<li>The routing setion in breaking changes <a href="/link/897c60dca21a4658b9df07414adbdf7c.aspx">document</a></li>
<li>The IPartialRouter interface and it’s sample code <a href="https://docs.developers.optimizely.com/content-cloud/v12.0.0-content-cloud/docs/example-of-news-partial-routing">document </a></li>
<li>IPartialRouter<TContent, TRoutedData> and PartialRouter<TIncoming, TOutgoing> class <a href="/CsClassLibraries/cms/EPiServer.Core.Routing.PartialRouter-2?version=12">document </a></li>
</ul>
<p><span style="font-weight: 400;">I read them a couple of time, tried a couple of way and finally, I found a possible way to do this stuff. <br />Now let go to the code and I will explain the solution in detail.</span></p>
<p><span style="font-weight: 400;">First of all, in the sample code for <a href="https://docs.developers.optimizely.com/content-cloud/v12.0.0-content-cloud/docs/example-of-news-partial-routing">News partial routing</a>, it said about IPartialRouter: <em>Make the partial router handle incoming requests beyond pages of type <strong>NewsContainer</strong>. Also, allow outgoing FURLs to be created for instances of <strong>NewsContent</strong>.</em> So which means we can use IPartialRoute for all the pages - <strong>PageData </strong>:) </span></p>
<p><span style="font-weight: 400;"><span>I folked the latest version of Foundation project and created <strong>MarketPageDataPartialRouting.cs</strong>, you can get it in my <a href="https://github.com/sondn2010/Foundation/blob/main/src/Foundation/Infrastructure/Routing/MarketPageDataPartialRouting.cs">github</a>.</span></span></p>
<p>In this file, there are 2 most important methods: </p>
<ul>
<li><span class="cm-variable"><strong>object RoutePartial(...)</strong> this method handles imcomming request</span></li>
<li><span class="cm-variable"><strong>PartialRouteData GetPartialVirtualPath(...)</strong> this method handles outgoing url</span></li>
</ul>
<p><img src="https://lh3.googleusercontent.com/pw/AL9nZEVQrln_pOKSco89myjGHzAJ3sT9DG_3VmRn3MEx73Ze3ojx2XyxIAV21szyqQjPIlrBVK6_XIKrLrPw6ZbAry4Cu29EmXsZ8rtQXDcLkAry28aS4050GwG00WEGgxfoY92kJHgcDa2NOhqOlkBBMIGyxQ=w1048-h1024-no?authuser=0" width="1000" alt="Members of IPartialRouter" height="977" /></p>
<p>In the <strong>GetPartialVirtualPath</strong> method, we will get the current market id and set to url, in current language.</p>
<p><img src="https://lh3.googleusercontent.com/pw/AL9nZEVachwJ1T8KLZ5KGTLq-fmkGAUFpixHz8IArO-a0J6h6wlkIhhqDqC17DLn34AazByUW6ARX3xUfHEC1NWRlpRM_d43yjzQIoGEipoRfIuXYwUMJoM7nsGZ83Yy80mOKU9NZt0gctJbgHpiY2obUrWTSw=w1062-h376-no?authuser=0" width="1000" alt="PartialRouteData GetPartialVirtualPath" height="354" /></p>
<p>This step help us create the link with language and market in our site.</p>
<p><img src="/link/fd6a64396d614857b4f9f958e52e3d23.aspx" /></p>
<p>In the <strong>RoutePartial</strong>, we will look for the market id in url segment and set it to current market if needed, then also process the rest of the content, returns correct content for that url.</p>
<p><img src="https://lh3.googleusercontent.com/pw/AL9nZEVa07gWioQj1GIaYaqnFjJ0mTYxsdVk7Wl-dDWIunfUx06maMKXGvgVsT_fnAwlCsAYbFPxJmsLGPcxt9GizW4yM-mDR8zW64OexYXo3gdYo0pNt6ACVBYxMO2WQdS-YYXbDZcnS0BT3DjR8uMC5puf7w=w1097-h984-no?authuser=0" width="1000" alt="RoutePartial and Process Market Segment" height="897" /></p>
<p>This step handles the incomming request with language and market in site url.</p>
<p><img src="/link/cd84433954664929ba1b74a66777933d.aspx" /></p>
<p>However, we need to register our Market partial routing to the site using <code>PartialRouteHandler.RegisterPartialRouter</code> to make the routing works as expected.</p>
<p><img src="https://lh3.googleusercontent.com/pw/AL9nZEXOKGSK-xcCI3LUQ1yhfWJhLiHNNT_6qJioOh7wsCyz9tHbQ5Z0j2D1M3d-GGJq0KWw4M94f-RQ3tM73m_HR9qG0Nees5KEOTTrEhRkOpjehlz_P34VxVlxjrha3tGwmINjXLzFYY-sgfkwIjNqZOHcPA=w1101-h328-no?authuser=0" width="1000" alt="Register Market routing" /></p>
<p>You can get the code in the Foundation project in my github, <a href="https://github.com/sondn2010/Foundation/blob/main/src/Foundation/Infrastructure/Routing/MarketPageDataPartialRouting.cs"><strong>MarketPageDataPartialRouting.cs</strong></a> file. And that is for CMS content, I'll create Commerce content in another post.</p>
<p>Bonus tip: we can use the <code>HttpContextExtensions.GetContentLink(HttpContext)</code> to get the current page content link :) it's useful.</p>
<p>Hope this make developer life easier :)) If you have any comment about the solution, could be better way, please let me know :)<br />Btw, I'm glad to be back to Optimizely Commerce after 3 years, a pretty long time :)</p>Find Personalization 13.2.1 supports EPiServer.Personalization.Common 3.0.0/blogs/Son-Do/Dates/2019/4/find-personalization-13-2-1-supports-episerver-personalization-common-3-0-0/2019-04-15T07:38:50.0000000Z<p>There is a couple of new features and breaking changes were defined in Episerver.Personalization.Commerce 3.0</p>
<p><a href="/link/2674da9576aa4c69be33443277802044.aspx">https://world.episerver.com/documentation/upgrading/episerver_personalization/episerver-personalization-commerce-3-0-breaking-changes/</a></p>
<p>In case your site that is <span style="display: inline !important; float: none; background-color: #ffffff; color: #000000; cursor: text; font-family: Verdana,Arial,Helvetica,sans-serif; font-size: 14px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-decoration: none; text-indent: 0px; text-transform: none; -webkit-text-stroke-width: 0px; white-space: normal; word-spacing: 0px;">using EPiServer.Find.Personalization and it </span>needs to be updated to new EPiServer.Personalization.Common 3.0.0, you will need to update Find Personalization 13.2.1 also.</p>
<p>However, the Find 13.2.1 is an unofficial release because the Find packages are based on the location of your indices service. Contact Episerver support team to get the packages <br /><a href="/link/3cf54037e7f3448b868d7bc10f3d9bb0.aspx" title="Upgrading Episerver Find">https://world.episerver.com/documentation/upgrading/episerver-find/find-13/upgrading-to-find-13-2-0-and-13-2-1/</a>.</p>
<p>Regards,</p>
<p>/Son Do</p>EPiServer Find – smooth indexinghttp://sodo.employee.episerver.com/?p=3982019-03-19T08:02:30.0000000Z<p>In this article, I summary several ways optimizing Find indexing. That may solve some repeatedly questions from customers and support team for a couple of years. </p>EPiServer Find – indexing big attachment contenthttp://sodo.employee.episerver.com/?p=3872019-01-10T05:22:55.0000000ZWe faced the issue with content data (content with an attachment file PDF, mp4, …). The limitation of an indexing request to Find is ~43MB, so we couldn’t add that content to Find index. However, we found a work around for this case. We won’t index the content binary data, instead of that, meta data […]Find 13.0.3 supports Newtonsoft.Json 11/blogs/Son-Do/Dates/2018/9/find-13-0-3-supports-newtonsoft-json-11/2018-09-25T06:08:46.0000000Z<p>We want to notice that the Find 13.0.3 was released last week. It includes an important change that supports Newtonsoft.Json 11 officially.</p>
<p>And the list of bugs that were included in Find 13.0.3 can be found here:<br /><a href="/link/76974ad8d2a84c1b989ad0ac453ab663.aspx?versionFilter=13.0.3&packageFilter=EPiServer.Find&typeFilter=All">https://world.episerver.com/documentation/Release-Notes/?versionFilter=13.0.3&packageFilter=EPiServer.Find&typeFilter=All</a></p>Sorting issue in Episerver Find 13.0.1/blogs/Son-Do/Dates/2018/7/sorting-issue-in-episerver-find-13-0-1/2018-07-26T07:14:08.0000000Z<!DOCTYPE html>
<html>
<head>
</head>
<body>
<p>We have known an issue about <strong>sorting in Find 13.0.1</strong> that we can reproduce</p>
<ul>
<li>Create a developer index in find.episerver.com.</li>
<li>Create some properties for string, DateTime, int type for a page type</li>
<li>Create some page using those page types.</li>
<li>Run the site and index the content.</li>
<li>In the test page, create query and sort by Date or SortNumber.</li>
</ul>
<pre class="language-csharp"><code>var query = _searchClient.Search<BasePage>()
.FilterForVisitor()
<strong>.OrderBy(x => x.MyOwnDate)
</strong> .Select(x => new SearchItem
{
PageTitle = x.Name,<br /> MyOwnDate = x.MyOwnDate,
SortNumber = x.SortNumber,
TeaserText = x.TeaserText,
ID = x.ContentLink.ID
})
.Take(100);
model.SearchResults = query.GetResult().Hits.Select(x => x.Document).ToList();</code></pre>
<p>And the bug is published <a href="/link/1933ba72787346df9003b7a4c7d1cff8.aspx?epsremainingpath=bug/FIND-3596">https://world.episerver.com/support/Bug-list/bug/FIND-3596</a> and will be fixed in next version.</p>
<p>While waiting for upgrading to the next version, we can have a way of working around:</p>
<pre class="language-html"><code><strong>.OrderBy(x => x.MyOwnDate, null, null, false);</strong>
// Or
<strong>.OrderBy(x => x.MyOwnDate, null, SortOrder.Ascending, false);</strong>
// For order descending, we had:
<strong>.OrderBy(x => x.MyOwnDate, null, SortOrder.Descending, false);</strong></code></pre>
<p>We need to change OrderBy clause and make sure that the <strong>ignoreUnmapped</strong> parameter is <strong>false</strong>.</p>
<p>We can apply the same working around for other types: date, int, ...</p>
<p>Hope this helps your work.</p>
<p>/Son Do</p>
</body>
</html>Personalization Find template - practice/blogs/Son-Do/Dates/2018/4/personalization-find-template---practice/2018-04-09T14:02:13.0000000Z<!DOCTYPE html>
<html>
<head>
</head>
<body>
<p>This post describes how Find integrated with Personalization, how Find boost the result based on behavior data from Personalization – in a sample of course 😊</p>
<h2>Preparation</h2>
<p>1. I used Quicksilver - Commerce sample site for this post.</p>
<p>2. Add configuration from your Personalization UAT to the site <img src="https://lh3.googleusercontent.com/hM1ruJzh-9w7JzDNdNyl6T1k7VGJ8NjqlxuJOHUGjeCXcAkp6D1Kpfa-p2_WipW_952N0PdkQFGdCAI_K6QdbPTtOkOyPf_uHKxC_2bDWYKww4A0H3z8FGv3YuyQc1-sFeEy1tD1eLHegXZV00iWFqRKPiCrjYMsUJgM3ROTTRs11GAjo_CM--7EpmO0KLWDqoxv7ygVPsTdoQerQ-Y-VQ35UVkhKH0yvJakoUEjUtiZRvFxaphnQF6MaFWmUwfHsImZw9c8zRdgUaOMz6qFqi4C6j60gHChmFccc56zM7oPl4xC0fphLimiZVxjuNSPRpqA0NMIh9zpN6LLFx2I-r1f4KtVR0cJPo6p5gAtB-EJIY8LS6cysOcZch2o710C8D82HhZcQeL0fyBZIPJ7jS6txcWMWSwgmWIuD1UCnyQXx2UurKftQYN6cC6rr1Cwu8pWaFDEL3zlb-Y7D9P8k0OB3F8Mv7MfvrlMOkCQxxpVbRRcND2I7ZqJBoG7XWJQS2RSaF0lL8bidisngeuWcIx0TxI9SaJ6ReA9PMcdEFHqPXO9r6epapWs16c6UDiaqjPWPjzvcftth1qZ2Nl1g4uLoHkRCul4N7jSi6D3jd4oX1yAhw2y2MfL5d0QEjbv3aRVyAi_zzaHH2lEAbVIUKxPzEOcy0NSkg=w1277-h247-no" alt="Personalization configuration" /></p>
<p>3. Make sure that the recommendation worked as expected. <img src="https://lh3.googleusercontent.com/XiJMVocWQKvQrgF7zsFo6j89RtxssWzqszYcQGAYR4rQDg0V8lgEww5_6BaOKnCQjMcBtnDsfjYEHTGARaOuRbB9PRMIekDLtVcVVASyW8e7UFWYgs_h00pBBz3FKAF1Q2Z4BAunAmkObFNrWEzINwi44YWQhLVJk_w1z0Ws4ydo7dEYIrd6_ict8Obc_zlYHf8vry13KuBBjIfH0lfOFSmBFZWICJEouDhafBhMpALWBsfOTFN754RqauVTpLlFsAvG03DwDwpJv8Qsz06ZQO5Yr2j85AJu00gILUbJc5GtL9ZJMAfCO-1URordD2T1_w3uiMEReSjS3uB5oOQQNYKF1SQxF3srk-_f_NL30vsfesM880gVleyuHpnGjzmWLTWAWMd7pLgj0gvGgYCK0dcpUyiwDubtJJBjvDUQ77mjuwHIDvCpHnuWgabrLiFVcinvr-EPGuRb4egBsVRoQg2uFSC4tp1aOtuut3Xx0A9jacDEX6IxvLxPxOPjlWBbPYnAjFgQauFyZ2F80o1ZrWi4n-JkQ48wi41n3rcUP6XJv56VnQLz3HtIwccbjSRL_qYn9O-NxERENTlRYOadlm9kk5ReKCaMZoEjLRC1O_ee09m_s3UXgS8tDVBcWqkvCVAao6zuP6hqEUztvjyQck_hTdwRXFLGNw=w1300-h1213-no" alt="Recommendation on Quicksilver" /> 4. View some product pages, so Personalization system can understand your behavior.</p>
<h2>Verify the Personalization Find</h2>
<p>1. Create a new personalization find test page.</p>
<p>2. Regarding the Personalization Find <a href="/link/17595d656a2a46c5834814ee14d5f1fd.aspx">document </a> and Jonas' <a href="/link/44198eeaf71040ef8281f32b9c532079.aspx">blog post</a>, the template looks like this:</p>
<pre class="language-csharp"><code>private readonly IClient _client;
private readonly UrlResolver _urlResolver;
public PersonalizedFindPageController(IClient client, UrlResolver urlResolver)
{
_client = client;
}
public ActionResult Index(PersonalizedFindPage currentPage, string q, bool? usePersonalization = null)
{
_client.Personalization().Refresh();
var prefData = _client.Personalization().Conventions.PreferenceRepository.Load();
}</code></pre>
<p> </p>
<p>4. Debug or create a new view for viewing the prefData variant, the UI should have something like this:</p>
<p><img src="https://lh3.googleusercontent.com/EF6M-wnvpIzmpYKSvh1rmHxCz8uzII1fF716nxwruBRzaBy_fLvDjP8aDAZZDJYGqIdyNiIeelTSVs8_gaDvHltg4M2cOhpMaOnVi1Ymibq9ogvwKqMeR_s38COVwmNGFDHZtwqTaE0_AnzgFOwMkPmTNP9mS9mMZHFT3XhqreQeTWDpUiOncWH3gkOMRy9iwxr59HaIA0jCVSsZ5Yld5KRmlSUb1swZq0xslyLzps5t4p6uNibeb7W2V5sBVJ2E0axFoRYrbtoLqNtMIx-wZaCtEBSEip6uE8XAAyTlGgqGrIWPurk8aHKSChIvb81x8xIy1F0NqoT8bKl3XUXNg1P1q7AvNaOsc6BFHvUWFNMd1XxxUl0FAgQPDD3skmdZjxgqJfZoliiFGcCp93eXCWbMcRUcnZFRlA4l2BOB2hDGc2yRUb2o58bDLwsxNBz-GEf2gKb-mVWJ1NaXi74IGO5w7JElWEKVeKq9ys6i5MM_G6M6r4DJHwE6Vu276sWa6Ctbbc-UzfGsEdwr5N5TO3onujFO34ry5grKJbJLpiTpmQF8YvsN4Qa2F1M1nE6esFYU046TuiOLsawb0B51-SHOnpFKp6gEgDznw8UHQrJkUqhzzCOm87ecFD9tpTuLUe1OdjTFD2Coa3YCvmuXfyisoNTLbTB73A=w794-h269-no" alt="Personalization attributes" /></p>
<p>5. Make sure that the return attributes aren't empty. Then we can get the Personalization search result by <code>.UsingPersonalization()</code> for your current search query.</p>
<pre class="language-csharp"><code>var resultsPersonalized = _client.Search<FashionProduct>()
.For(q)
.FilterForVisitor()
.UsingPersonalization()
.GetContentResult();</code></pre>
<p>6. Note that we can compare the search results by using personalization and without using personalization:</p>
<pre class="language-csharp"><code>var resultsPersonalized = _client.Search()
.For("shoe")
.FilterForVisitor()
.UsingPersonalization()
.GetContentResult();
var resultsWithoutPersonalized = _client.Search()
.For("shoe")
.FilterForVisitor()
.GetContentResult();</code></pre>
<p> The search results have different orders based on the above attributes - that's customer behavior. <img src="https://lh3.googleusercontent.com/wOxhUGi3MlTOydqPo71mi56MBpUDDTttmVDYgHMAxV7kfd0hO7xWT6ZdHDZBIgb2ip4LvLo2gqP-QbPtpmsWPA_0arsUtBK0KIBI92QTwwQ49h36rAkB8l0eh6zamd6nYMzMVFLjN-X9zsif8fz2zrR-KNpzwXqRkoKcNFqNXdDJGLh7JXqB4_35u5e9ALE_GPDr_Og5UrOPFMKfmOAJGxwYrtCqOO2m82EHnflqBi8d2Jj4RK0iqOy-bSVmaw86MAfQ-BtWWMsXVR4nGk7e4oLWiTH_x5o4-KmeOqIgjOs1nfE3nrmYZNR_1CBSFcYDQe_-CDEKtBb1tXOLWRjnIE0zbM8ZA_daq93bE4WpizbzxD96Ht42TucRb_lV8lqcuTgcvnFRBQD2be2qwezYrVoGh0pZHmMVDfX-o-ze6uVdppF9dO3XWw_Kee01RIp8CuRV4E3nb-J1lfmBY9Z4zpkBs90bwagpfSHPLusx0mxLKRYUvWlV2x9tP0Diau1UgYPXmdUE-u7eJziCwu413iFLZLrBgty_kvHT10hFWGjwaQxcVJdcYCwqy7dusb1O_d18cyDWL6TQLrrRynz5R5EnANFsT6kg34J0M-WxmqBfzNScF89QFUeihcfzKXxwZC4Dz1z0aD1fsMSBGAFqcdw_ahAJ_3NG6g=w1150-h753-no" alt="" /> That is how Personalization Find works.</p>
<p>This practice code is kept in this compressed file: <a href="/link/874df0d379e944b182dc5b3896b1ce55.aspx">PersonalizedFind.Template.zip</a>, feel free to play with it.</p>
<p> </p>
<p>Just note that Recommendation and Personalization Find are applied to Commerce content only at this time. And <strong>.UsingPersonalization()</strong> on the query, it only boosts Commerce content properties. I hope this feature will be applied to CMS content soon - waiting for that (Y)</p>
<p> </p>
<p>Hope this help.</p>
<p>/Son Do</p>
</body>
</html>Episerver Find synonyms – practicehttp://sodo.employee.episerver.com/?p=3412018-03-10T04:32:01.0000000ZThe purpose of this post is I want to practice Find apis – Synonym, refer to this document. The exercise this time is creating a tool for creating all English synonyms. The English synonyms resource I found at this website and I try to gather them to a list of useful Episerver Find synonyms. Step by step 1. […]Release shipment jobhttp://sodo.employee.episerver.com/release-shipment-job/2017-04-28T16:34:51.0000000ZRelease shipment job (known as Shipment Releasing) help managing order easier. By default, this job wasn’t actived and weknow that Episerver could set it execute automatically in a specific time. Using this job, our order shipments will be released after 1 day placing order. However, 1 day is just default configuration and we could set […]Release shipment jobhttp://sodo.employee.episerver.com/?p=2992017-04-28T16:34:51.0000000ZRelease shipment job (known as Shipment Releasing) help managing order easier. By default, this job wasn’t actived and weknow that Episerver could set it execute automatically in a specific time. Using this job, our order shipments will be released after 1 day placing order. However, 1 day is just default configuration and we could set […][ServiceAPI] New users-groups cannot login and use service apihttp://sodo.employee.episerver.com/serviceapi-new-usersgroups-cannot-login-and-use-service-api/2017-04-20T07:15:51.0000000ZI will note those steps for re-use because I got some questions about new users – that was added to WebAdmins group but they cannot use service api. Environment Quicksilver with service api installed. We will use PostMan tool to verify this issue. In this guide, we’ll use existing user editor@example.com in WebAdmins group. Reproduce the issue First, […][ServiceAPI] New users-groups cannot login and use service apihttp://sodo.employee.episerver.com/?p=2392017-04-20T07:15:51.0000000ZI will note those steps for re-use because I got some questions about new users – that was added to WebAdmins group but they cannot use service api. Environment Quicksilver with service api installed. We will use PostMan tool to verify this issue. In this guide, we’ll use existing user editor@example.com in WebAdmins group. Reproduce the issue First, […][Service API] install service api to QuickSilver – step by stephttp://sodo.employee.episerver.com/service-api-install-service-api-quicksilver-step-step/2017-04-20T06:41:07.0000000ZEnvironment Quicksilver site. Visual Studio 2015 or later version for our implementation. Install service api to QuickSilver Open QuickSilver solution via VS 2015. Open Package Manager Console, and type install-package EPiServer.ServiceApi.Commerce to command line. Make sure that we get EPiServer.ServiceApi.Commerce in package.config file. Open Startup.cs, add AspNet Identity config: [crayon-58f840d94c2f9503396671/] Final step is adding config […][Service API] install service api to QuickSilver – step by stephttp://sodo.employee.episerver.com/?p=2822017-04-20T06:41:07.0000000ZEnvironment Quicksilver site. Visual Studio 2015 or later version for our implementation. Install service api to QuickSilver Open QuickSilver solution via VS 2015. Open Package Manager Console, and type install-package EPiServer.ServiceApi.Commerce to command line. Make sure that we get EPiServer.ServiceApi.Commerce in package.config file. Open Startup.cs, add AspNet Identity config: [crayon-598a6d2c40fac054636763/] Final step is adding config […]Payment providers with abstraction apis (10.5.0 – 2017)/blogs/Son-Do/Dates/2017/4/payment-payment-providers-with-abstraction-apis-10-5-0--2017/2017-04-15T11:22:31.9730000Z<!DOCTYPE html>
<html>
<head>
</head>
<body>
<p>Episerver Commerce released payment providers packages in 10.5.0 (include PayPal, DIBS, DataCash). Those packages are available for <a href="/link/60513f074cf049bfae0d279d92f99ddf.aspx">downloading</a> and installation document are under <a href="/link/ffbd07fbcb884a0da8419de193e17c5a.aspx">Commerce Payment providers sample</a> section.</p>
<p>This is the main changes in this packages:</p>
<h3>Payment providers use abstraction apis</h3>
<p>The payment gateway now use abstraction apis - that support both Legacy carts and <a href="/link/2b7b481e527c40c1baaa0c224082b886.aspx">Serializable carts</a>.</p>
<p><img src="https://lh3.googleusercontent.com/Tq0OISOgITy9PcXAmfzXyR44-3ZviHUi4V1RNdNqv_4OIGkstIfCjVr-DjV1D47X2krHqe4qXQsP1eh_fKiOna1Afwcj8PRgeBOG3H2ZtOxtjqpn2j_niv-0CM482QJzUrE84JnpiiSlWIh3ELDQF2RYEE-MgKUh7RnY1eSszBfm5FpayidLI-9-hZZabKwZzZBjyWAdVA5yMmAYG2CH9Tto66q8RuCSXPiFN-AqGqMi_zmzXcJV5Runjsd5L29sNzsAfRXgOrCH6O2G56P_mwukB8GtSPWsbAemnlWzK17nlGa5r2VmzZJigRYnlAT_isqpP1Moc_Ks-Cu0jgQ7yFLTzNwIP1p9KjtiGubt9t1iQDUk_kZsK9fbe9n9a-ZvQMo9kHwns1XyYXaGJCXvwfuOGt3NAZYTWFOig1mMcwBWnS4wKxANXByS7ytNOkm5sxfhGRsGi_X6zZ5wRmvf12kJzdJJlbPf_dCoKPZfTVo863mYJsqDCoWJ7w_RjbiBChE0z9HtGftLJKzl6MHU1ibw8ZS019PJDTASZemnOujGrN3Glz7m6NT3s7Rk7DJ73OTxWbx4ibd1MdKoGtA1TCnu_KDyGoDaWPiT6CF8ATPBCYhcaJMj-tJzdCzDmpmrjOKOx8Wm7uYKYPuEasvp34R5AjDzoSnKF7dls9eodR4=w1002-h111-no" alt="Payment migration" /></p>
<h3>Pretify payment providers sample code and testable</h3>
<p>The payment gateway has over 1200 lines of code before, now it was separted to smaller class for better readable and testable.</p>
<p><img src="https://lh3.googleusercontent.com/wx87l20-yW5iNf0-RQVOUXUyNsMb2FZbBBylYTkX3kveAEYTxBU74Xv5g5Gz4vlxTBVHr8sw5lNcHdykKq-mom_usO6Ownr0hE-3c1JN_QRJHv8Ds7mdxFsPc5THYn17h8848PVEHwJtwTxlxLNYsmQ4C999Kr0oftiDtw8t589CZkqxSlrtvDYPItT50whIdFJXRNJb2Z8Q0-p1wZIcLsgBkre0wSrKTdn36YyyDwi6vJpgtCPG8lTQ_2-7ZA-1_oU2XuwjT-d7HzQcY-JfK0IIlpfpLNXR85nQULms-DmhXDsIIr2HLt3KgA510y0MDlzNXUj29zlp-_ES9Z_xCczceK9yvMVBO4anGSPETYHnA2AwA5MBdSJivH_9ilNT1n7hH1vYwo74teLQvsHCTvKFqQ4l1hT_4Jf9UnjomczkCzv6d-KDkvCNYcLigJimax3i545vRvLUj3PfejOi3A7EI_-pIo0PdglsCtCKZYut5lTLby5paahm4XtLxjK_HaIisxnuJcq90on3OjGrO3Yyqrt2_YqtiYermrJLygnTmmjNgmtrJtERLVSuPaZsepc7MVTWlnTpmvnlYvF5G28MyarKw46-DfUmIB2PUXJDx9pBu3U75n5qw0UHQqaSZGVGoQHuQY1-K95gsXbFkmX21cpAGUA_Ev_6bts2kS4=w1132-h667-no" alt="Pretify payment code" /></p>
<h3>Payment providers are testable</h3>
<p>Most of function in payment project are testable now. Adding test project to payment solution to see how it worked.</p>
<p><img src="https://lh3.googleusercontent.com/B-4_iMhM_jZKxbVwenoH6Bg2C8tNrFfax4rVaW7m3k4I4ps9P76rP5p9lpgoNmts2wHv4dgcwE46hGPpYcL2FUrcKBp-c0eYnY-GDaxcoEhDjWRROeSJtVa8X0FDyk-e8QVjuamgUGwO9Jk49BU3AOZqd1Q7Q341DzT48eNpj_Kj1xUEVveiI5SPQqnb4n8F0i-mSjLTnW3cqfqy43ylMpdss9l8UyuI_EWm6bCw0cidahiEgCq3ix1jUcIZRTkH7vkti3DDDKyhluElAqgu0I8mDQEGsHinQrD-0o2cWqFGXCwvu-PrOJP2twkwRDjlqp_6LT1PM3JRTp70fheo5xNU-Kvqh7OQe6-nm9-Jkwaws5GhovubBeP4DVR8xAwze7RIxz4KLvFWVyqZakrxBwWUlRdHf357VDTCydB0kAlVJZlOakaSD21Rofxfm0KyIAi8p6UUaST9t0as3jkdFsJeE_sHuq4sknq7D-BK4ovwDwulz14Ixn_g2mne8kUJ-bOI_lJM0Ae5mySlKqKNFhvW8VYE12e5i1RoUo8v4mbrNv5dTU0K77rreoBPZe0B7UY7eSrl6qSoWaTqSTlVcmKyhm--Pjse3uk052MTvgUfgZruDRdUUD9IGvZkt7zEaZnJMcm8lF1cSeBegS5cAf4ficaCqldYu1mJMbujFTg=w1537-h858-no" alt="Payment are testable" /></p>
<h3>Use MVC</h3>
<p>Those payment provider packages use MVC from now on. With MVC, the steps to deploy a payment provider to website much easier than before.</p>
<p><img src="https://lh3.googleusercontent.com/KJopJMF_rMbeTXumSs0C91UNiPBeDkJJWmgJrfFOatwv3t1HfwVq0TTY0DmrCJkPVGCz8zXpQTGG5w__iOS-0trb-_2UGhwCXnhL5AHV1U4n9rEcVU4C0ykeJ2GBaFakkEE0e1CASwrqS4-rjp2SJPC2IvGtZEezR8Mroex-6NpE_H3egRpOpLfP7lCdN4pouuLXdRLyz6292lNzvYUh39R5W-ZRUk9K4IaLCEVasCJHzo4tl1croSni22P9ySLD6nclWpXdBY-3FfPGDoiA6PqmLFxkV02z2lWt8wecySUbWt2B9E8vWUAgQ5_PM0YKiYcfEdsnqTKFieXG8VRfCGwkGJtr997Zzn7XylTlEJ5DdhNSU8MmCml2F9XHIZ8PcysdGUtAI7CuP-9WTQXDShxxl244wuHrgooLWTekE3vBsqMUx51eNcUyERbnXBrarOjYU4KuC4P6bPW0_UzQ2cscjCC4GwSjAklua4aHMT1jh3R76w7bASO3t3DAAX2aB6Osn9RniaGvmdze_5b2LvQ0kaU2o0XpgvuZS1v-sokliIWQ222VWH6TUg_ZtnbWUDuS-E313uI6U3yNpBnv0iZ79_izmeDDrca5OAuipSOLW8Ngudim9m0M0Z1IVBzHGf-5rwRzlAPD_ZtaozIRtB7_zxPXOzyVcp4aqtm79-c=w970-h315-no" alt="Payment MVC báe" /></p>
<h3>Images for payment page type</h3>
<p><img src="https://lh3.googleusercontent.com/Fn_Ev5UJmwx94UWQM__Rk0AXjxeuO0EXnYOgEcKjNJ_OK32bnS7hoA3mo9Ty9FL-VBLBUvHPrLoBWo8fXjlUzrYqyeXUAL1YY-G5LS62ot8KjtIlFaKa531lDbknTCqcD85-27dL13jGd53Xa3YnguMzzAZzFxb0p0G2iPkSXQhaZRib84pCQkXb8kWtKV4pJ-wOawMm52QauspRM4AQCYRec2KQIrGvEhMCTT71HPHr8EKkx2a3sCAQbg6Sr4d7iF6qZ000_08Nw0wX1g04AbeBMiM5vKdcr6s9RN6VvOKyNNRHhSid_BUE9Fixw0jBDXIvUS3bKdUlBIKUskxr-GXX6VxNxvOFHWrQbKXSbOhRNgXNbqDKhr1vySVHwfmRu6ZXneQfbbAf85p37IXVLbZIfWcwHm6G14ib41nEq_AUKmOjyJ3EcLtqJx_h9ANOxWcX0jid48CgIY1gfWbp6lt8oZongdkIiuD5yxNfKiu4hEOpAxE4729Ga9J4CR8JX2SGnSIlB4pADfC6NvWNdawLR7WrTYlG9N0GVR2layliZbnPNpZ5HYl8MIQRdoV1T9fq-eahxeKDRnodx5u0x_RPy6Gt_fOmU9U_XJnQBQ-rRJv-Rwl0UM_TZ_UXfZjUr8ODx_mpclg0NXahTr04mNUHTueuOfz73OibOQDR28E=w1338-h355-no" alt="Payment page type logo" /></p>
<h3>DIBS: new responsive payment UI</h3>
<p>DIBS API provide a <a href="http://tech.dibspayment.com/D2/Hosted/Styling/New_layout">new layout</a> for responsive payment page.</p>
<p>And in our DIBS payment project, we're using simplest way</p>
<pre>requestPaymentData.Add("decorator", "responsive");</pre>
<p>That tells DIBS that we use responsive page.</p>
<p><img src="https://lh3.googleusercontent.com/f_WUIVMkWrjg2KnPtEYyQR5i0z7NFfUsvRdnzp43PLbz0H7P18gW-Ua3zuQ3GIoMVMAiB4PlPjyuUQm1QKHhKbh7p-Rwkm9166QjHewYZS5JV7BOCY2qLCYyqjwgLDPHiTDAr8QP3pwkoQOSz9ebPdNQPOn2X2MlyUi9n3tkfp0wA4noT18ssuKSi9kfujvLC4-hoFaMawqcqaRY51MZch9liEKEjFNOBjHiOQotYGaeoqVHvApHy8SkzW1jslLTnEo2AVxZrWGN1pg6J0cREK6bfzHKyGzvU9UznakrcCUI9qPY86kgVmz9BiWstg5NxQ_lZ5feYNCulY9Tw9MWhTa4GEZKeaj8ksv_jaYnTmpqXvv4RL4PoxYZ8zB7BAz55lnqtM9RGmYPvngpSXmp7rp_yQ5ed0FyXoeF8lRd20IfnhCDfOG1Bk3J7pT6tGuAeC6DNpZ8mSMwV-Z0OBQx99hEFnBMOjiEY1cgdhh78VceNnBOb_qVu-vj2whrG7o0Q2zemU_UYocJHDPfPfY1J_5hZXc1THkwP-wOmwnbDIyAXm9cdkNTNbVnJOFe_4FEKiC8yuws1_SlKEha4UcZ76jcfh3CuZNZRAGVcJm9qWnRiwitCGhh1CiO-SOO5JMAyukaeJrltV4JXabsgM0uTRbhChYknc782gR-fDQTNLU=w1208-h621-no" alt="DIBS responsive layout" /></p>
<h3>DataCash: redirection to payment page instead iframe on site</h3>
<p><img src="https://lh3.googleusercontent.com/YaA4LyQzNWTk9EEZyQqlmsJN74obeN27ICvQ8fuUMQrqvGCxWOuxj-xLz4xkmTkTKtcHp5a4njSdU9aSJPoiykPQsDhmIoHvsCtePDR9CyJJ1iNvX9m1XE4uRXZqEc_iYm3trWrRmPQjkR8IA6QT3AHItfn-yScM1V8GrVBSmeR46p4EG4EYZitiZ34YnS9qDdj7Te1Uejxd9TBv7aTQxXJTo9JtU7Al4khZurNg6htPseyUaO3ehkJjRn2vN1jpybNvcoq_iEbO-w5wqknJK59dLMIkv1jHemOElyJasRZZ_dyAI7mBsHoXhxR9VVFudCZ0IBdmfo91kzFjdeQEfZWY3QIyj4ntJCeT77DwnAkl2B7oyGNfv6-Ugh5q3ed99iu_tDODC6fQy3SakushK6LFrdE9ZFVc_ll3BGU7Ow8TVzTFI0WnuSONcuZAGQLOydtDU2RVv6S4xW6COCRIaKj3PEz2fKUGUJwe50rqiWZZ8KT_ahS1Sqoh94LqE3rNFX9oMVXFAHGcCH8EnBTXrPpKaI7kihwTzA3hbeVL_p2UmWCNQtZVD8_xGfWBwAJYML60FNP5jI-q9nJEqZBZTaBaV31hiCmWFawUUnuUDHGCrbERChwPaYnZDJvj85UqbVbWZgaUcyMKSCN_U-Lnkg8_ghp0N4dgvKUy3qyKUkU=w1325-h375-no" alt="DataCash redirection" /></p>
<h3>PayPal: support tax changed, order total changed after changing address on PayPal</h3>
<p><img src="https://lh3.googleusercontent.com/FiqoPI1mT_saOslP6i5G8TPCdCRMxNnR9e8oMp1kL5u6WPANhaUAnx3LAUJa92CO9kQD_gZF33lDhRHsqRdQeceyH-7ZkYOUCq7bXUPT-45AltN7MCusPkDUH8ztK3fbAMKMg8iEVVtsrbZtlQrcihYGQN4pycqsRF32_UpuQ4ZnT8IeCyoubBEtbrGrhNInrFzLa0Bz294V1vvpd6UsPmm8sY58VGm2UcngA9GPU9qPNKs1WXd6O0zYLEcwA-UAiYYpLhZIKrA8QEzOUNPoTeJpK4tOkJINdCRqmqF1GhC9OgcAthxNy91TMIRO-wzys53JqEk0z6krN-I1_C5_06luEUEIEefFeQPJyZnKerfoQPDcYWBCGmcxjNSRP-f-5lOZv3gS5uDhxaVzS-i13F4U0YiT3vLjlopfOXlLfW1IL_9DGdahTPn1oDrono1848dx45cZnrbA1Uryy56E-PlBJmiLPr7hLAdN1kPhcYqUFY903v4B3Konv2lCZus3MLN8ufBXBO7-gGJCWT7VXLN_Qjejm9bmfIQgllz1ICXMY4QJe1miA0Q8-Wg4XZztZGkKJUjS_Z4dKXuD2J58wH6tUvYTSNM2FcanUSl9ucKmnuSlWlrVE4Z_jY98H8o-4DQei5-9TebsLs4kl9vcn-_8YI6qljrcrJtZ85GF_uE=w1284-h784-no" alt="support review address changed" /></p>
</body>
</html>[SerilizableCart] Migrating IPaymentGateway using abstraction - IPaymentPlugin/blogs/Son-Do/Dates/2017/1/SerilizableCart-migrating-ipaymentgateway-using-abstraction-ipaymentplugin/2017-01-23T10:55:48.5200000Z<p>As you know, the SerializableCart was released in Episerver Commerce 10.2.0. This improves performance a lot. However, before using SerializableCart, we need to first complete some tasks.</p>
<p>This post describes how to <b>"Convert IPaymentGateway to IPaymentPlugin using abstraction classes"</b> - the <b>most important</b> task that needs to be done when enabling SerializableCart mode.</p>
<p>SerializableCart only supports abstraction and no-workflow. So our Payment providers also need to use abstraction or ... you will break your payment :).</p>
<h2 id="s_environment">Environment</h2>
<ul>
<li>Visual Studio 2013 or later version for our implementation.</li>
<li>This guide uses open source payment provider from <a href="/link/60513f074cf049bfae0d279d92f99ddf.aspx" target="_blank">Episerver</a> - <b>DIBS payment provider</b>.</li>
</ul>
<p><img class="img-thumbnail" title="DIBS payment provider" alt="DIBS payment provider" src="https://lh3.googleusercontent.com/z2PLmzRTnaeJKlphm2_ihkXYxxhBTuWjRxCVvUmb9XZ3paPttPlNO4Av-tP0zy4rLEat6Gcyh5RziT_xqwIXTc9MuGs-BgImznLhtIHr8KzmITKjBOrSNfAOsuZ8Go-DMEx5ialZKWXz9KCRVm1ls6GhAVWlgN61JoDnhxiYy6rvaQDkLEGqZyasA9wcKeasC71BW9qM6zgx6X2SnKga0vjZhSv9SFmTt34c21t4bvQ_rZ5yHPT9WEBeIYk6g-Z2gaMqXF8LoI3NpOcqtsTYpuIBn_yGiK194J3ujZMQy0rhiMhgj2eLKPSwVSV6XMbQVWsfG23ofixfzC9bAQyn7YGlRPHyPoMJMtg_0u3Il4mE6AF3ndjhRKnHfqSWYyZpljMtjeKqI4IQszWtfhrdzNqnZQJIVtieVxanmaPKgvV2uJ8VeHv3pNmvb_JE0W65w-vpAv8asRXIC9Q54RelY_wtjCuxzk1xguRGMCYu9mHglps9vnfS6zy_TlFf6OwR4MWryg5qQdU-fwEHMFTtsGvuNhUE9iGqwUUBrVNyuPEhO-3eoT1ctAkK5yTs39C5e4Lu46rIZRuhZsxSUZ2_4Rk1vf28Ufj8tiDXKIRAPObrEG4wqR5CEulEj8r_wy76ug6ei-Q6mvVzNnZV1i24M-aBhfmrQ3e5VoRAZ-visQM=w365-h151-no" /></p>
<p><i>We could do the same with other payment providers.</i></p>
<h2 id="s_upgrading">Upgrading payment provider project.</h2>
<ol>
<li>Open the payment provider project.</li>
<li>In the Package Manager Console, install the latest Episerver.Commerce.Core package (at least 10.2.3)</li>
</ol>
<p><img class="img-thumbnail" title="Upgrading latest Commerce.Core" alt="Upgrading latest Commerce.Core" src="https://lh3.googleusercontent.com/183HPBiUdRhdsbYZBzo8hXrAi0GRncOWJm_b0ZQCyz1-vSfsp3Yay2aK-9siU6qaTu361bf68CUZK_uB0OE8_vMgtbKyOGlIe5unuA7wj_jEn9uWpFrjne7bAs6iM9wKP9iDignk-Q6Pwd1-lhe3mpj0JtQGjKQGNzZV8zIrLQWXbJwYD-UzjSLDOxzkIztkriHxi2DHyhln6dyIfqdO5x-O9eue29ifPoLp9TULm5TcfLYtv8eJD0_1q31JuDWcJGkZdqjECepKZRTJFFSZbPVwC3rJb5mXnmNH7FvpMZC-cXNrIAvzzVj7iYZU8fIz8QJOgiY6UxtNt2Ht7FflEl35P7lwjepX7dbsduw38_5_ZqJ4uf6pcQfFRLMSq6Ns0gjNhh5msz8elwy02IoNcNP8_RikRKP2LqMEhPVRJvNlJtxJ5HhxfRDR9CCKqgtUWQIy4BfvKG5aBdvt48Heb6yp8QOlGmxNoIhpwF_C6CIYLBIDGLBUZQEuQOlQdNxbBbK1wv4TlelCgnqo_8_YUZx4aTijOlQhyj6D0wajrRHkyVv1poK8u7yu2HpJK8jYeJu_bZhHpVy4joapUnSTEbVAleixGoBWXjGaakx2uotNbFJoUzE5rk_62gzex0VNPoEv2i8mHpgk8a7SNBof6nZQgP9oRGdrEzWRM9lzxQ0=w732-h208-no" /></p>
<h2 id="s_upgrading">Migrating payment gateway.</h2>
<h4 id="s_implementinterface"><strong>Making our payment gateways implement the IPaymentPlugin interface</strong></h4>
<p><img class="img-thumbnail" title="Migrating payment gateway" alt="Migrating payment gateway" src="https://lh3.googleusercontent.com/OAtBfXQhu2gq0k87wzAG-ivys72ciuGTSrsXtDdc5w5IfI5jYP-n4rjino7VFUQEz2BdYJNLy5vcMnFvjqzyKJ0tw6h1eHBGoAzKC8IpEvyYAJWmsP8od1yrbb8dTegoPcfi_PouALenJt-1s2dKdWPIfrw56CIhR1Q5JYvf_8XYp05skkw1V3qU9s4DxW_wY0Tx3XfcqOeR6Z8YUUzBdLFRK4lUJ8ztzUecV7ateLQqx0-C_SjsHO_bBErxZ4MWlj-KDBgfN4MjwYfPXxh8xWHLjgHuvI-NptaUMPwAnywEeCJJ1l7IzzQGaRN-J4lSwFGZql3xjMmA168sj1jVDXDLf8cupzgLvenoTaUNrYvU-3E8OqXQ8tmKpm5e9UVSvPXfzW_n6Bls0rV8cvRL0DZfnLpSkAp02BXHZ56Zit8kaSg1CE0e2WU7t7a_Sp74TNFUwHVvryIjC4-qhhCvC9Jbrlw47RwTVKi-QqTxsa0bK4_OYoSqK8v_ZuT4JFNC9qLgb6_Bwb-w5fArY76mnWytV_hnJoHtWn_Tr1tJ-3lZsNs1LNbUyqlYjMZHGyssnrQYCdQmzm6EJi9QED-HTZX8FzGjDbIubI9P8jILsleXwDZ4T1RUBM2CjakxgNa5UGIALiroR725JW8FVLZKowcLyhYbjQ82pkD5OmKzv3c=w626-h158-no" /></p>
<p><img class="img-thumbnail" title="Migrating payment gateway 2" alt="Migrating payment gateway 2" src="https://lh3.googleusercontent.com/G3TFGyzLP_tfSXe1mJCp_F4BgwxxDfQg5mPkUu7bsIGmDX9uqxU7hWd5zpKW25AZlGPJOKYk5oPAZAQ8vSQf27F2n7L53u1OaNA5OAiZghxJrAuYb_OL-4bbWdLctCDPlGGtGTILuYHoSDu2vmaFKBnnFY7IUCbQ2AmbsC_7sOTQxtZMyMrWudH0_MYv-ETqFV1FJE7h312sKj0-zMg3wsKy-yx6ADsIExjLoCoZe0PheGq8pXVX42n-awMKMELw9d2V18eFEGgmgmPLgYeo1K8EWN8IxjLWfSYEXjVx1ZtzYJKPXm3IhHRb2iCXcPWhwd3fTS37xCk0if0S1yZanJMYJl1_gDrjUFtXG-pU37ZbDD3sXzAm3vNQJDstKtOcejFAsMV_D9V8sG2NW578TbhfBE7O-OqegfhQCkqjVd7xa44LhCTs6L7tgIETV6njd4xMg-o3qjVC1-jWqpo9KJTxVYAW8jQn-KFBikR0GX9ZZRG_8NgCjJChSj01QRJBnqZ_bOVaNxCtTgzxN67nw1HrBftFxb7YeHRc-Z-TUEr09gBiLtUvPvjgJoa-4ezCFuJyUoo8R3d-AYaZFEjLqwtthawmXYSu3r-WAX3Bgx0kq7atvOrhNxVW2SMdPPjDth7gp7UHE-yfqkxCGOd54Iz391PZ-5I_qn0BopP_F8U=w792-h178-no" /></p>
<pre>public bool ProcessPayment(IPayment payment, ref string message)
{
throw new NotImplementedException();
}</pre>
<h3 id="s_addingproperty">Adding IOrderGroup property</h3>
<pre>public IOrderGroup OrderGroup {get; set;}</pre>
<h2><span class="text-info small">Why we do this</span></h2>
<p>The <span class="text-danger">EPiServer.Commerce.Order.IPayment</span> interface is quite different from <span class="text-danger">Mediachase.Commerce.Orders.Payment</span>. The IPayment interface doesn't have <span class="text-danger">Parent.Parent</span> or <span class="text-danger">Parent</span> methods like <span class="text-danger">Payment</span>, so we have to add IOrderGroup to our payment provider property - <span class="text-danger">IOrderGroup</span> will be used instead of <span class="text-danger">Payment.Parent.Parent</span>.</p>
<h4 id="s_changeoldmethod">Change old implementation method</h4>
<p>Move all code in ProcessPayment(Payment payment, ref string message) to the new method ProcessPayment(IPayment payment, ref string message) and add the following code:</p>
<pre>private IOrderForm _orderForm;
public override bool ProcessPayment(Mediachase.Commerce.Orders.Payment payment, ref string message)
{
OrderGroup = payment.Parent.Parent;
_orderForm = payment.Parent;
return ProcessPayment(payment as IPayment, ref message);
}</pre>
<p><span class="text-info small">Why we do this <br /></span>Those lines of code support <span>compatibility</span> with workflow payment process.</p>
<p>And <span class="text-danger">ProcessPayment(IPayment payment, ref string message)</span> method should be like this:</p>
<div class=" clearfix"> </div>
<div id="codeB1" class="panel-group">
<div class="panel panel-success">
<div id="samplecodeB1" class="panel-heading"><span class="panel-title small"><a href="#samplecodeB1a" data-toggle="collapse" data-parent="#codeB1">DIBSPaymentGateway.cs - public bool ProcessPayment(IPayment payment, ref string message)</a></span></div>
<div id="samplecodeB1a" class="panel-collapse collapse">
<div class="panel-body">
<pre>public bool ProcessPayment(IPayment payment, ref string message)
{
if (HttpContext.Current != null)
{
if (payment.Parent.Parent is PurchaseOrder)
{
if (payment.TransactionType == TransactionType.Capture.ToString())
{
//return true meaning the capture request is done,
//actual capturing must be done on DIBS.
string result = PostCaptureRequest(payment);
//result containing ACCEPTED means the the request was successful
if (result.IndexOf("ACCEPTED") == -1)
{
message = "There was an error while capturing payment with DIBS";
return false;
}
return true;
}
if (payment.TransactionType == TransactionType.Credit.ToString())
{
var transactionID = payment.TransactionID;
if (string.IsNullOrEmpty(transactionID) || transactionID.Equals("0"))
{
message = "TransactionID is not valid or the current payment method does not support this order type.";
return false;
}
//The transact must be captured before refunding
string result = PostRefundRequest(payment);
if (result.IndexOf("ACCEPTED") == -1)
{
message = "There was an error while refunding with DIBS";
return false;
}
return true;
}
//right now we do not support processing the order which is created by Commerce Manager
message = "The current payment method does not support this order type.";
return false;
}
Cart cart = payment.Parent.Parent as Cart;
if (cart != null && cart.Status == PaymentCompleted)
{
//return true because this shopping cart has been paid already on
//DIBS
return true;
}
var pageRef = DataFactory.Instance.GetPage(PageReference.StartPage)["DIBSPaymentPage"] as PageReference;
PageData page = DataFactory.Instance.GetPage(pageRef);
HttpContext.Current.Response.Redirect(page.LinkURL);
}
return true;
}</pre>
</div>
</div>
</div>
</div>
<h4 id="s_migratingnewmethod">Migrating new method</h4>
<p>In <span class="text-danger">ProcessPayment(IPayment payment, ref string message)</span> and related methods, we have to use abstraction instead Legacy Cart.</p>
<p>We have some "rules" for our migration:</p>
<table class="table table-striped table-hover">
<thead>
<tr><th width="40%">Old way</th><th width="40%">Abstraction</th></tr>
</thead>
<tbody>
<tr>
<td>Mediachase.Commerce.Orders.Payment</td>
<td>EPiServer.Commerce.Order.IPayment</td>
</tr>
<tr>
<td>Payment.Parent</td>
<td>_orderForm</td>
</tr>
<tr>
<td>Payment.Parent.Parent</td>
<td>_orderGroup</td>
</tr>
<tr>
<td>payment.Parent.Parent.BillingCurrency</td>
<td>_orderGroup.Currency</td>
</tr>
<tr>
<td>Cart</td>
<td>ICart</td>
</tr>
<tr>
<td>Cart.Id</td>
<td>ICart.OrderLink.OrderGroupId</td>
</tr>
<tr>
<td>CartHelper</td>
<td><i>(removed)</i> - use ICart (_orderGroup) instead</td>
</tr>
<tr>
<td>CartHelper(Cart.DefaultName).Cart</td>
<td>IOrderRepository.LoadCart(CustomerContext.Current.CurrentContactId, Cart.DefaultName)</td>
</tr>
<tr>
<td>cartHelper.PrimaryAddress</td>
<td>payment.BillingAddress</td>
</tr>
<tr>
<td>cartHelper.FindAddressByName(payment.Parent.BillingAddressId); <i>Billing Address</i></td>
<td>payment.BillingAddress</td>
</tr>
<tr>
<td>cartHelper.FindAddressByName(payment.Parent.Shipments[0].ShippingAddressId) <i>Shipping Address</i></td>
<td>_orderGroup.GetFirstShipment().ShippingAddress</td>
</tr>
<tr>
<td>Cart.LineItems or payment.Parent.LineItems</td>
<td>_orderForm.GetAllLineItems()</td>
</tr>
<tr>
<td>LineItem.ExtendedPrice</td>
<td>ILineItem.GetExtendedPrice()</td>
</tr>
<tr>
<td>OrderAddress.AcceptChanges()</td>
<td><i>(removed)</i></td>
</tr>
<tr>
<td>Payment.AcceptChanges()</td>
<td><i>(removed)</i></td>
</tr>
<tr>
<td>Cart.AcceptChanges() or PurchaseOrder.AcceptChanges()</td>
<td><i>(removed)</i> - use IOrderRepository.Save() instead</td>
</tr>
<tr>
<td>Cart.OrderForms[0].Payments</td>
<td>ICart.GetFirstForm().Payments</td>
</tr>
<tr>
<td>payment.Parent.TaxTotal or OrderForm.TaxTotal</td>
<td><i>(removed)</i> - use ITaxCalculator instead</td>
</tr>
<tr>
<td>Cart.Total or PurchaseOrder.Total</td>
<td>use IOrderGroup.GetTotal() instead</td>
</tr>
</tbody>
</table>
<p>After migration, our <span class="text-danger">ProcessPayment(IPayment payment, ref string message)</span> method will be like this:</p>
<div class=" clearfix"> </div>
<div id="codeB2" class="panel-group">
<div class="panel panel-info">
<div id="samplecodeB2" class="panel-heading"><span class="panel-title small"><a href="#samplecodeB2a" data-toggle="collapse" data-parent="#codeB2">public bool ProcessPayment(IPayment payment, ref string message)</a></span></div>
<div id="samplecodeB2a" class="panel-collapse collapse in">
<div class="panel-body">
<pre>public bool ProcessPayment(IPayment payment, ref string message)
{
if (HttpContext.Current == null)
{
return true;
}
if (OrderGroup is PurchaseOrder)
{
if (payment.TransactionType == TransactionType.Capture.ToString())
{
//return true meaning the capture request is done,
//actual capturing must be done on DIBS.
string result = PostCaptureRequest(payment);
//result containing ACCEPTED means the request was successful
if (result.IndexOf("ACCEPTED") == -1)
{
message = "There was an error while capturing payment with DIBS";
return false;
}
return true;
}
if (payment.TransactionType == TransactionType.Credit.ToString())
{
var transactionID = payment.TransactionID;
if (string.IsNullOrEmpty(transactionID) || transactionID.Equals("0"))
{
message = "TransactionID is not valid or the current payment method does not support this order type.";
return false;
}
//The transact must be captured before refunding
string result = PostRefundRequest(payment);
if (result.IndexOf("ACCEPTED") == -1)
{
message = "There was an error while refunding with DIBS";
return false;
}
return true;
}
//right now we do not support processing the order which is created by Commerce Manager
message = "The current payment method does not support this order type.";
return false;
}
var cart = OrderGroup as ICart;
if (cart != null && cart.OrderStatus.ToString() == PaymentCompleted)
{
// return true because this shopping cart has been paid already on DIBS
return true;
}
_orderRepository.Service.Save(OrderGroup);
var pageRef = DataFactory.Instance.GetPage(PageReference.StartPage)["DIBSPaymentPage"] as PageReference;
PageData page = DataFactory.Instance.GetPage(pageRef);
HttpContext.Current.Response.Redirect(page.LinkURL);
return false;
}</pre>
</div>
</div>
</div>
<div class="panel panel-success">
<div id="fullcodeB2" class="panel-heading"><span class="panel-title small"><a class="collapsed" href="#fullcodeB2b" data-toggle="collapse" data-parent="#codeB2">DIBSPaymentGateway.cs</a></span></div>
<div id="fullcodeB2b" class="panel-collapse collapse">
<div class="panel-body">
<pre>public class DIBSPaymentGateway : AbstractPaymentGateway, IPaymentPlugin
{
public const string UserParameter = "MerchantID";
public const string PasswordParameter = "Password";
public const string ProcessingUrl = "ProcessingUrl";
public const string MD5Key1 = "MD5Key1";
public const string MD5Key2 = "MD5Key2";
public const string PaymentCompleted = "DIBS payment completed";
private string _merchant;
private string _password;
private PaymentMethodDto _payment;
private readonly Injected<IOrderRepository> _orderRepository;
private IOrderForm _orderForm;
public IOrderGroup OrderGroup { get; set; }
public override bool ProcessPayment(Mediachase.Commerce.Orders.Payment payment, ref string message)
{
OrderGroup = payment.Parent.Parent;
_orderForm = OrderGroup.Forms.FirstOrDefault(form => form.Payments.Contains(payment));
return ProcessPayment(payment as IPayment, ref message);
}
public bool ProcessPayment(IPayment payment, ref string message)
{
if (HttpContext.Current == null)
{
return true;
}
if (OrderGroup is PurchaseOrder)
{
if (payment.TransactionType == TransactionType.Capture.ToString())
{
//return true meaning the capture request is done,
//actual capturing must be done on DIBS.
string result = PostCaptureRequest(payment);
//result containing ACCEPTED means the request was successful
if (result.IndexOf("ACCEPTED") == -1)
{
message = "There was an error while capturing payment with DIBS";
return false;
}
return true;
}
if (payment.TransactionType == TransactionType.Credit.ToString())
{
var transactionID = payment.TransactionID;
if (string.IsNullOrEmpty(transactionID) || transactionID.Equals("0"))
{
message = "TransactionID is not valid or the current payment method does not support this order type.";
return false;
}
//The transact must be captured before refunding
string result = PostRefundRequest(payment);
if (result.IndexOf("ACCEPTED") == -1)
{
message = "There was an error while refunding with DIBS";
return false;
}
return true;
}
//right now we do not support processing the order which is created by Commerce Manager
message = "The current payment method does not support this order type.";
return false;
}
var cart = OrderGroup as ICart;
if (cart != null && cart.OrderStatus.ToString() == PaymentCompleted)
{
// return true because this shopping cart has been paid already on DIBS
return true;
}
_orderRepository.Service.Save(OrderGroup);
var pageRef = DataFactory.Instance.GetPage(PageReference.StartPage)["DIBSPaymentPage"] as PageReference;
PageData page = DataFactory.Instance.GetPage(pageRef);
HttpContext.Current.Response.Redirect(page.LinkURL);
return false;
}
/// <summary>
/// Posts the request to DIBS API.
/// </summary>
/// <param name="payment">The payment.</param>
/// <param name="url">The URL.</param>
/// <returns>A string contains result from DIBS API</returns>
private string PostRequest(IPayment payment, string url)
{
WebClient webClient = new WebClient();
NameValueCollection request = new NameValueCollection();
var po = OrderGroup as PurchaseOrder;
string orderid = po.TrackingNumber;
string transact = payment.TransactionID;
string currencyCode = OrderGroup.Currency;
string amount = Utilities.GetAmount(new Currency(currencyCode), payment.Amount);
request.Add("merchant", Merchant);
request.Add("transact", transact);
request.Add("amount", amount);
request.Add("currency", currencyCode);
request.Add("orderId", orderid);
string md5 = GetMD5KeyRefund(Merchant, orderid, transact, amount);
request.Add("md5key", md5);
// in order to support split payment, let's set supportSplitPayment to true, and make sure you have enabled Split payment for your account
// http://tech.dibspayment.com/flexwin_api_other_features_split_payment
var supportSplitPayment = false;
if (supportSplitPayment)
{
request.Add("splitpay", "true");
}
else
{
request.Add("force", "yes");
}
request.Add("textreply", "yes");
webClient.Credentials = new NetworkCredential(Merchant, Password);
byte[] responseArray = webClient.UploadValues(url, "POST", request);
return Encoding.ASCII.GetString(responseArray);
}
/// <summary>
/// Posts the capture request to DIBS API.
/// </summary>
/// <param name="payment">The payment.</param>
/// <returns>Return string from DIBS API</returns>
private string PostCaptureRequest(IPayment payment)
{
return PostRequest(payment, "https://payment.architrade.com/cgi-bin/capture.cgi");
}
/// <summary>
/// Posts the refund request to DIBS API.
/// </summary>
/// <param name="payment">The payment.</param>
private string PostRefundRequest(IPayment payment)
{
return PostRequest(payment, "https://payment.architrade.com/cgi-adm/refund.cgi");
}
/// <summary>
/// Gets the payment.
/// </summary>
/// <value>The payment.</value>
public PaymentMethodDto Payment
{
get
{
if (_payment == null)
{
_payment = PaymentManager.GetPaymentMethodBySystemName("DIBS", SiteContext.Current.LanguageName);
}
return _payment;
}
}
/// <summary>
/// Gets the merchant.
/// </summary>
/// <value>The merchant.</value>
public string Merchant
{
get
{
if (string.IsNullOrEmpty(_merchant))
{
_merchant = GetParameterByName(Payment, DIBSPaymentGateway.UserParameter).Value;
}
return _merchant;
}
}
/// <summary>
/// Gets the password.
/// </summary>
/// <value>The password.</value>
public string Password
{
get
{
if (string.IsNullOrEmpty(_password))
{
_password = GetParameterByName(Payment, DIBSPaymentGateway.PasswordParameter).Value;
}
return _password;
}
}
/// <summary>
/// Gets the M d5 key refund.
/// </summary>
/// <param name="merchant">The merchant.</param>
/// <param name="orderId">The order id.</param>
/// <param name="transact">The transact.</param>
/// <param name="amount">The amount.</param>
/// <returns></returns>
public static string GetMD5KeyRefund(string merchant, string orderId, string transact, string amount)
{
string hashString = string.Format("merchant={0}&orderid={1}&transact={2}&amount={3}",
merchant,
orderId,
transact,
amount);
return GetMD5Key(hashString);
}
/// <summary>
/// Gets the MD5 key used to send to DIBS in authorization step.
/// </summary>
/// <param name="merchant">The merchant.</param>
/// <param name="orderId">The order id.</param>
/// <param name="currency">The currency.</param>
/// <param name="amount">The amount.</param>
/// <returns></returns>
public static string GetMD5Key(string merchant, string orderId, Currency currency, string amount)
{
string hashString = string.Format("merchant={0}&orderid={1}&currency={2}&amount={3}",
merchant,
orderId,
currency.CurrencyCode,
amount);
return GetMD5Key(hashString);
}
/// <summary>
/// Gets the key used to verify response from DIBS when payment is approved.
/// </summary>
/// <param name="transact">The transact.</param>
/// <param name="amount">The amount.</param>
/// <param name="currency">The currency.</param>
/// <returns></returns>
public static string GetMD5Key(string transact, string amount, Currency currency)
{
string hashString = string.Format("transact={0}&amount={1}&currency={2}",
transact,
amount,
Utilities.GetCurrencyCode(currency));
return GetMD5Key(hashString);
}
private static string GetMD5Key(string hashString)
{
PaymentMethodDto dibs = PaymentManager.GetPaymentMethodBySystemName("DIBS", SiteContext.Current.LanguageName);
string key1 = GetParameterByName(dibs, MD5Key1).Value;
string key2 = GetParameterByName(dibs, MD5Key2).Value;
MD5CryptoServiceProvider x = new MD5CryptoServiceProvider();
byte[] bs = System.Text.Encoding.UTF8.GetBytes(key1 + hashString);
bs = x.ComputeHash(bs);
StringBuilder s = new StringBuilder();
foreach (byte b in bs)
{
s.Append(b.ToString("x2").ToLower());
}
string firstHash = s.ToString();
string secondHashString = key2 + firstHash;
byte[] bs2 = System.Text.Encoding.UTF8.GetBytes(secondHashString);
bs2 = x.ComputeHash(bs2);
StringBuilder s2 = new StringBuilder();
foreach (byte b in bs2)
{
s2.Append(b.ToString("x2").ToLower());
}
string secondHash = s2.ToString();
return secondHash;
}
/// <summary>
/// Gets the parameter by name.
/// </summary>
/// <param name="paymentMethodDto">The payment method dto.</param>
/// <param name="name">The name.</param>
/// <returns></returns>
internal static PaymentMethodDto.PaymentMethodParameterRow GetParameterByName(PaymentMethodDto paymentMethodDto, string name)
{
PaymentMethodDto.PaymentMethodParameterRow[] rowArray = (PaymentMethodDto.PaymentMethodParameterRow[])paymentMethodDto.PaymentMethodParameter.Select(string.Format("Parameter = '{0}'", name));
if ((rowArray != null) && (rowArray.Length > 0))
{
return rowArray[0];
}
throw new ArgumentNullException("Parameter named " + name + " for DIBS payment cannot be null");
}
}</pre>
</div>
</div>
</div>
</div>
<p>Finally, we can rebuild the project, then follow the steps in this <a href="/link/74f925db7a6a44798778fa66a9690a4a.aspx" target="_blank">guide</a> to deploy to our development site and verify payment with SerializableCart.</p>
<p>The code in this blog post is available in my <a href="https://github.com/sondn2010/EPiServerCommerce_PaymentProviders" target="_blank">GitHub</a> as well - you could download it.</p>
<p> </p>
<p>Hope this helps.</p>
<p>/Son Do</p>How to enable and implement authorize.net on Quicksilver/blogs/Son-Do/Dates/2017/1/how-to-enable-and-implement-authorize-net-on-quicksilver/2017-01-11T02:37:20.6630000Z<p><a title="Authorize.net" href="http://www.authorize.net/" target="_blank">Authorize.net</a> is a built-in payment gateway in Episerver Commerce. However, in Quicksilver on <a title="Quicksilver on GitHub" href="https://github.com/episerver/Quicksilver" target="_blank">GitHub</a>, it wasn't enabled yet.</p>
<p>This post describes how to enable Authorize in Commerce Manager and implement this payment gateway in Front-end. We could use this for Quicksilver and your MVC site.</p>
<p>I list the main section below and you could pass through what you have already.</p>
<ul>
<li><a href="#enableCM">Enable Authorize.net in Commerce Manager</a></li>
<li><a href="#implementFrontEnd">Implement Authorize payment on front-end</a>
<ul>
<li><a href="#AuthorizePaymentMethodcs">AuthorizePaymentMethod.cs</a></li>
<li><a href="#AuthorizeViewModelcs">AuthorizeViewModel.cs</a></li>
<li><a href="#PaymentMethodViewModelResolvercs">PaymentMethodViewModelResolver.cs</a></li>
<li><a href="#paymentview">Adding payment view</a></li>
</ul>
</li>
</ul>
<div>
<h2 id="enableCM">Enable Authorize.net in Commerce Manager</h2>
<p>Login to your Commerce Manager with admin roles</p>
<p>Navigate to Administration > Order System > Payments > English</p>
<p>Click on New button to add new payment method</p>
<p><img class="img-thumbnail" title="Enable Authorize.net in Commerce Manager" alt="Enable Authorize.net in Commerce Manager" src="https://lh3.googleusercontent.com/hPVKqn-CH0U5hp37kDHPsEzgnz7cE6Laot0oeXCrvwKYHC3yhusRCeEU9OKwEbT2xIQIdk7kyBHL_pufCXCD0fzwZ7BCknYQ_8yr2H05ePWLxX3IOENeerk5IOPzIpFoCnlzh6jTdEUq2dcigXXbmLv0hZ_qwcEN4COojE7IjUnL6-ryHF-_Gh5WmmyolKYNIazpHD7AeP69ylUANSyPkLEpfmzZ0QT_YzyQmHazga8spQmERl26-Th1kMcrftDvSkcI6bsJzeCb6FZP8bs3vzjfd8DmGHx8V0sPZ8USfFndfqpYvPC4xSsdxZ9tzas7mdyMkLMG6OXFgKbTCxso4XbKEtBienfaBEYu6-kerFfBJMB9-NKpsx1VELgg1Lo37F4CZGYb5ZzHmTa3ELA8oT5mGnzc4daUpxpZgHdGkos1kAejHpZTI3XtGTrbsLxkMwkbNLsLM1uReRw3ga04KjEP7EpcmETviezjliWkX5U2gO55vQY3OagcEvTeYmxs3khSgremRDjCGG-1tHM00pGzqe4YZIjznC04qhRj4hD54skA3kUfRjTlCAdLM-ymAEtBELyqOXqZIZkFmPxCow06EcD03lWDbkMWjRx8RAAhyjafbckDNKz6gFTZJGK9Vmnsbci9cjhwdzt605_a8unjoleycye4esXudmFcucA=w680-h289-no" /></p>
<p>Adding Authorize to our commerce like below:</p>
<p><img class="img-thumbnail" title="Enable Authorize.net in Commerce Manager" alt="Enable Authorize.net in Commerce Manager" src="https://lh3.googleusercontent.com/5JFKUvieuP6H0it0aDjcjOC1ABcNE8qVhYh-OTlMWl5KKXY5TxuaJZwdylvSJCBjIzGqSifmsMOoQ1TxygygSDIn027k7pbLO_HCOhnsyODShDoqncxYsrHYRARjdJ2C_rh-ZGsBdxXI8MOeVQjWQZz0_iyAk6s8WyTZJquLPLPOhOwXMGjVp4aAVQLJ9qXIeqLS3JdJfK1VUx-vbTwXe-Ib7XdOc-e3Gr7uiwBLcSJN4wKeVsa4LSxsWxrxVNg6M066Kkcz5kSOIXRmCGLToTQqv3_3sL314Aju93D5jjEdBBuduSutRPPtfzjOBl5c9-tq3N33-qQHNgl7aXyqPbE2K1-8N0v0CW3VqqzbR4kO1k5MPchkRM0p-2vEE6ykoSGiiihuD17TbEXEmQb2iAMVWVwF8UD20R8LFOz2NKzocIX_t6nUfXdxHJDP1VFzFn3rTOI1xgnwRvxpSrzwJQav4DlZHxw68IvidVxRme4s5wxY6YI5xRsHpdY_IzqXlSfQF8qWTU8NVzNL6QTt7VifUUT02zqzJwBUHWyvarcDOSuAph95oRM3BieszHnLnzzSWWsJX1_7Jq1wtER9iv22HizAWX9ThMauai4vrM17tCtbg9bGhd2tNUFouLC4WjJXx324-l6W8yr01ElengvOQE8Fk2OE74Xjr5vLWLg=w595-h630-no" height="630" width="595" /></p>
<p>Open Front-end, adding an item to cart and checkout, we will see the error:</p>
<p><img class="img-thumbnail" title="Enable Authorize.net in Commerce Manager" alt="Enable Authorize.net in Commerce Manager" src="https://lh3.googleusercontent.com/oxsH2TGSsPFhORMKiX54NgFN22Vsj6Db2MgOAmow-fZTmRqvKUI4YlrPIhzXOyl4A1BdevETv_RyEvBE8TwezXv5s6jJ9cS101Am6IBZdN-p9DI2U7vwcvtToMQCFgmFGyX7AVV7GsCdRxuoQBIsh_q7-coP2goATd9Mr1xQZey6Vyzh0WNkjLHk63PsftC12WWxmpSAqYg1eWtvMOmpJF3quFFOOcweuL0T7JpLlpOVRneH3JeHUVTSNxwmZQSNFIlTFrzcjZxXnYCpKJyxP9D7DHKsSvwgnciJqk5yhX_dzoxxcO6n_WpLKo6IZ7ZxtfEd1N4_OgDuLu2EbNXIaPRA_TJalRyT8GDOp8zcV7PvIfDadwb7_-IpdnFZ2sB8sJ7mmud0Oz8BRHJV_bWDhoj1UwT6yfFpIoi-9Jc3MnUchqz0bKZaxnBTXhESZnVyfbSclWzYqbbjAUCqXLWdQ5upaOM8zX2g8JXOlk95nTCZZJXpitcNsdjoW4SPkLHou6rnbnwIdqLFrMtkJbABJJhlS7S8mgAE_L8b0tmRUKdGpOkFby2hrR5NnzeYffG_qDIzf_CR-oSK8kO48PP5G9Km6Oqbl_mrJYwcRq_857-1Jzmr-EUxRHi_f7S4gUQKhOGOaEr9uyILEHAnqtVDJxx_-kDPUNNlWgZXpzFBV3Q=w620-h192-no" /></p>
<h2 id="implementFrontEnd">Implement Authorize payment on front-end</h2>
<h3 id="AuthorizePaymentMethodcs">Implement AuthorizePaymentMethod</h3>
<p>In Quicksilver, payment base class ready for adding new method so we could use it <b>PaymentMethodBase</b>.</p>
<div class="clearfix"> </div>
<div id="code0" class="panel-group">
<div class="panel panel-success">
<div id="samplecode0" class="panel-heading"><span class="panel-title small"><a href="#samplecode0a" data-toggle="collapse" data-parent="#code0">AuthorizePaymentMethod.cs</a></span></div>
<div id="samplecode0a" class="panel-collapse collapse">
<div class="panel-body">
<pre>public AuthorizePaymentMethod()
: this(LocalizationService.Current, ServiceLocator.Current.GetInstance())
{
}
public AuthorizePaymentMethod(LocalizationService localizationService, IOrderGroupFactory orderGroupFactory) : base(localizationService, orderGroupFactory)
{
}
public override IPayment CreatePayment(decimal amount, IOrderGroup orderGroup)
{
throw new NotImplementedException();
}
public override void PostProcess(IPayment payment)
{
throw new NotImplementedException();
}
public override bool ValidateData()
{
throw new NotImplementedException();
}</pre>
</div>
</div>
</div>
</div>
<p>We need some properties for verifying a credit cart: holder name, number, month/year expiration, security code.</p>
<div class="clearfix"> </div>
<div id="code1" class="panel-group">
<div class="panel panel-info">
<div id="samplecode1" class="panel-heading"><span class="panel-title small"><a href="#samplecode1a" data-toggle="collapse" data-parent="#code1">Adding code</a></span></div>
<div id="samplecode1a" class="panel-collapse collapse in">
<div class="panel-body">
<pre>[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/CreditCardName")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/CreditCardName")]
public string CreditCardName { get; set; }
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/CreditCardNumber")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/CreditCardNumber")]
public string CreditCardNumber { get; set; }
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/CreditCardSecurityCode")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/CreditCardSecurityCode")]
public string CreditCardSecurityCode { get; set; }
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/ExpirationMonth")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/ExpirationMonth")]
public int ExpirationMonth { get; set; }
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/ExpirationYear")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/ExpirationYear")]
public int ExpirationYear { get; set; }</pre>
</div>
</div>
</div>
<div class="panel panel-success">
<div id="fullcode1" class="panel-heading"><span class="panel-title small"><a class="collapsed" href="#fullcode1b" data-toggle="collapse" data-parent="#code1">AuthorizePaymentMethod.cs</a></span></div>
<div id="fullcode1b" class="panel-collapse collapse">
<div class="panel-body">
<pre>public class AuthorizePaymentMethod : PaymentMethodBase
{
public AuthorizePaymentMethod()
: this(LocalizationService.Current, ServiceLocator.Current.GetInstance())
{
}
public AuthorizePaymentMethod(LocalizationService localizationService, IOrderGroupFactory orderGroupFactory) : base(localizationService, orderGroupFactory)
{
}
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/CreditCardName")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/CreditCardName")]
public string CreditCardName { get; set; }
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/CreditCardNumber")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/CreditCardNumber")]
public string CreditCardNumber { get; set; }
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/CreditCardSecurityCode")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/CreditCardSecurityCode")]
public string CreditCardSecurityCode { get; set; }
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/ExpirationMonth")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/ExpirationMonth")]
public int ExpirationMonth { get; set; }
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/ExpirationYear")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/ExpirationYear")]
public int ExpirationYear { get; set; }
public override IPayment CreatePayment(decimal amount, IOrderGroup orderGroup)
{
throw new NotImplementedException();
}
public override void PostProcess(IPayment payment)
{
throw new NotImplementedException();
}
public override bool ValidateData()
{
throw new NotImplementedException();
}
}</pre>
</div>
</div>
</div>
</div>
<p>CreatePayment method, we will use abstraction to create IPayment for cart</p>
<div class="clearfix"> </div>
<div id="code2" class="panel-group">
<div class="panel panel-info">
<div id="samplecode2" class="panel-heading"><span class="panel-title small"><a href="#samplecode2a" data-toggle="collapse" data-parent="#code2"> Added code </a></span></div>
<div id="samplecode2a" class="panel-collapse collapse in">
<div class="panel-body">
<pre>public override IPayment CreatePayment(decimal amount, IOrderGroup orderGroup)
{
var payment = orderGroup.CreateCardPayment(_orderGroupFactory);
payment.CardType = "Credit card";
payment.PaymentMethodId = PaymentMethodId;
payment.PaymentMethodName = "Authorize";
payment.Amount = amount;
payment.CreditCardNumber = CreditCardNumber;
payment.CreditCardSecurityCode = CreditCardSecurityCode;
payment.ExpirationMonth = ExpirationMonth;
payment.ExpirationYear = ExpirationYear;
payment.Status = PaymentStatus.Pending.ToString();
payment.CustomerName = CreditCardName;
payment.TransactionType = TransactionType.Authorization.ToString();
return payment;
}</pre>
</div>
</div>
</div>
<div class="panel panel-success">
<div id="fullcode2" class="panel-heading"><span class="panel-title small"><a class="collapsed" href="#fullcode2b" data-toggle="collapse" data-parent="#code2">AuthorizePaymentMethod.cs</a></span></div>
<div id="fullcode2b" class="panel-collapse collapse">
<div class="panel-body">
<pre>public class AuthorizePaymentMethod : PaymentMethodBase
{
public AuthorizePaymentMethod()
: this(LocalizationService.Current, ServiceLocator.Current.GetInstance())
{
}
public AuthorizePaymentMethod(LocalizationService localizationService, IOrderGroupFactory orderGroupFactory) : base(localizationService, orderGroupFactory)
{
}
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/CreditCardName")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/CreditCardName")]
public string CreditCardName { get; set; }
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/CreditCardNumber")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/CreditCardNumber")]
public string CreditCardNumber { get; set; }
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/CreditCardSecurityCode")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/CreditCardSecurityCode")]
public string CreditCardSecurityCode { get; set; }
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/ExpirationMonth")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/ExpirationMonth")]
public int ExpirationMonth { get; set; }
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/ExpirationYear")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/ExpirationYear")]
public int ExpirationYear { get; set; }
public override IPayment CreatePayment(decimal amount, IOrderGroup orderGroup)
{
var payment = orderGroup.CreateCardPayment(_orderGroupFactory);
payment.CardType = "Credit card";
payment.PaymentMethodId = PaymentMethodId;
payment.PaymentMethodName = "Authorize";
payment.Amount = amount;
payment.CreditCardNumber = CreditCardNumber;
payment.CreditCardSecurityCode = CreditCardSecurityCode;
payment.ExpirationMonth = ExpirationMonth;
payment.ExpirationYear = ExpirationYear;
payment.Status = PaymentStatus.Pending.ToString();
payment.CustomerName = CreditCardName;
payment.TransactionType = TransactionType.Authorization.ToString();
return payment;
}
public override void PostProcess(IPayment payment)
{
throw new NotImplementedException();
}
public override bool ValidateData()
{
throw new NotImplementedException();
}
}</pre>
</div>
</div>
</div>
</div>
<p>In PostProcess method, we will get card information that was inputted</p>
<div class="clearfix"> </div>
<div id="code3" class="panel-group">
<div class="panel panel-info">
<div id="samplecode3" class="panel-heading"><span class="panel-title small"><a href="#samplecode3a" data-toggle="collapse" data-parent="#code3">Added code</a></span></div>
<div id="samplecode3a" class="panel-collapse collapse in">
<div class="panel-body">
<pre>public override void PostProcess(IPayment payment)
{
var creditCardPayment = (ICreditCardPayment)payment;
var visibleDigits = 4;
var cardNumberLength = creditCardPayment.CreditCardNumber.Length;
creditCardPayment.CreditCardNumber = new string('*', cardNumberLength - visibleDigits)
+ creditCardPayment.CreditCardNumber.Substring(cardNumberLength - visibleDigits, visibleDigits);
}</pre>
</div>
</div>
</div>
<div class="panel panel-success">
<div id="fullcode3" class="panel-heading"><span class="panel-title small"><a class="collapsed" href="#fullcode3b" data-toggle="collapse" data-parent="#code3">AuthorizePaymentMethod.cs</a></span></div>
<div id="fullcode3b" class="panel-collapse collapse">
<div class="panel-body">
<pre>public class AuthorizePaymentMethod : PaymentMethodBase
{
public AuthorizePaymentMethod()
: this(LocalizationService.Current, ServiceLocator.Current.GetInstance())
{
}
public AuthorizePaymentMethod(LocalizationService localizationService, IOrderGroupFactory orderGroupFactory) : base(localizationService, orderGroupFactory)
{
}
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/CreditCardName")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/CreditCardName")]
public string CreditCardName { get; set; }
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/CreditCardNumber")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/CreditCardNumber")]
public string CreditCardNumber { get; set; }
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/CreditCardSecurityCode")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/CreditCardSecurityCode")]
public string CreditCardSecurityCode { get; set; }
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/ExpirationMonth")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/ExpirationMonth")]
public int ExpirationMonth { get; set; }
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/ExpirationYear")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/ExpirationYear")]
public int ExpirationYear { get; set; }
public override IPayment CreatePayment(decimal amount, IOrderGroup orderGroup)
{
var payment = orderGroup.CreateCardPayment(_orderGroupFactory);
payment.CardType = "Credit card";
payment.PaymentMethodId = PaymentMethodId;
payment.PaymentMethodName = "Authorize";
payment.Amount = amount;
payment.CreditCardNumber = CreditCardNumber;
payment.CreditCardSecurityCode = CreditCardSecurityCode;
payment.ExpirationMonth = ExpirationMonth;
payment.ExpirationYear = ExpirationYear;
payment.Status = PaymentStatus.Pending.ToString();
payment.CustomerName = CreditCardName;
payment.TransactionType = TransactionType.Authorization.ToString();
return payment;
}
public override void PostProcess(IPayment payment)
{
var creditCardPayment = (ICreditCardPayment)payment;
var visibleDigits = 4;
var cardNumberLength = creditCardPayment.CreditCardNumber.Length;
creditCardPayment.CreditCardNumber = new string('*', cardNumberLength - visibleDigits)
+ creditCardPayment.CreditCardNumber.Substring(cardNumberLength - visibleDigits, visibleDigits);
}
public override bool ValidateData()
{
throw new NotImplementedException();
}
}</pre>
</div>
</div>
</div>
</div>
<p>ValidateData method, we will verify card data that was inputted</p>
<div class="clearfix"> </div>
<div id="code4" class="panel-group">
<div class="panel panel-info">
<div id="samplecode4" class="panel-heading"><span class="panel-title small"><a href="#samplecode4a" data-toggle="collapse" data-parent="#code4">Added code</a></span></div>
<div id="samplecode4a" class="panel-collapse collapse in">
<div class="panel-body">
<pre>static readonly string[] ValidatedProperties =
{
"CreditCardNumber",
"CreditCardSecurityCode",
"ExpirationYear",
"ExpirationMonth",
};
public override bool ValidateData()
{
foreach (string property in ValidatedProperties)
{
if (GetValidationError(property) != null)
{
return false;
}
}
return true;
}
private string GetValidationError(string property)
{
string error = null;
switch (property)
{
case "CreditCardNumber":
error = ValidateCreditCardNumber();
break;
case "CreditCardSecurityCode":
error = ValidateCreditCardSecurityCode();
break;
case "ExpirationYear":
error = ValidateExpirationYear();
break;
case "ExpirationMonth":
error = ValidateExpirationMonth();
break;
default:
break;
}
return error;
}
private string ValidateExpirationMonth()
{
if (ExpirationYear == DateTime.Now.Year && ExpirationMonth < DateTime.Now.Month)
{
return _localizationService.GetString("/Checkout/Payment/Methods/CreditCard/ValidationErrors/ExpirationMonth");
}
return null;
}
private string ValidateExpirationYear()
{
if (ExpirationYear < DateTime.Now.Year)
{
return _localizationService.GetString("/Checkout/Payment/Methods/CreditCard/ValidationErrors/ExpirationYear");
}
return null;
}
private string ValidateCreditCardSecurityCode()
{
if (string.IsNullOrEmpty(CreditCardSecurityCode))
{
return _localizationService.GetString("/Checkout/Payment/Methods/CreditCard/Empty/CreditCardSecurityCode");
}
if (!Regex.IsMatch(CreditCardSecurityCode, "^[0-9]{3}$"))
{
return _localizationService.GetString("/Checkout/Payment/Methods/CreditCard/ValidationErrors/CreditCardSecurityCode");
}
return null;
}
private string ValidateCreditCardNumber()
{
if (string.IsNullOrEmpty(CreditCardNumber))
{
return _localizationService.GetString("/Checkout/Payment/Methods/CreditCard/Empty/CreditCardNumber");
}
return null;
}
</pre>
</div>
</div>
</div>
<div class="panel panel-success">
<div id="fullcode4" class="panel-heading"><span class="panel-title small"><a class="collapsed" href="#fullcode4b" data-toggle="collapse" data-parent="#code4">AuthorizePaymentMethod.cs</a></span></div>
<div id="fullcode4b" class="panel-collapse collapse">
<div class="panel-body">
<pre>public class AuthorizePaymentMethod : PaymentMethodBase
{
static readonly string[] ValidatedProperties =
{
"CreditCardNumber",
"CreditCardSecurityCode",
"ExpirationYear",
"ExpirationMonth",
};
public AuthorizePaymentMethod()
: this(LocalizationService.Current, ServiceLocator.Current.GetInstance())
{
}
public AuthorizePaymentMethod(LocalizationService localizationService, IOrderGroupFactory orderGroupFactory) : base(localizationService, orderGroupFactory)
{
}
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/CreditCardName")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/CreditCardName")]
public string CreditCardName { get; set; }
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/CreditCardNumber")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/CreditCardNumber")]
public string CreditCardNumber { get; set; }
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/CreditCardSecurityCode")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/CreditCardSecurityCode")]
public string CreditCardSecurityCode { get; set; }
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/ExpirationMonth")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/ExpirationMonth")]
public int ExpirationMonth { get; set; }
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/ExpirationYear")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/ExpirationYear")]
public int ExpirationYear { get; set; }
public override IPayment CreatePayment(decimal amount, IOrderGroup orderGroup)
{
var payment = orderGroup.CreateCardPayment(_orderGroupFactory);
payment.CardType = "Credit card";
payment.PaymentMethodId = PaymentMethodId;
payment.PaymentMethodName = "Authorize";
payment.Amount = amount;
payment.CreditCardNumber = CreditCardNumber;
payment.CreditCardSecurityCode = CreditCardSecurityCode;
payment.ExpirationMonth = ExpirationMonth;
payment.ExpirationYear = ExpirationYear;
payment.Status = PaymentStatus.Pending.ToString();
payment.CustomerName = CreditCardName;
payment.TransactionType = TransactionType.Authorization.ToString();
return payment;
}
public override void PostProcess(IPayment payment)
{
var creditCardPayment = (ICreditCardPayment)payment;
var visibleDigits = 4;
var cardNumberLength = creditCardPayment.CreditCardNumber.Length;
creditCardPayment.CreditCardNumber = new string('*', cardNumberLength - visibleDigits)
+ creditCardPayment.CreditCardNumber.Substring(cardNumberLength - visibleDigits, visibleDigits);
}
public override bool ValidateData()
{
foreach (string property in ValidatedProperties)
{
if (GetValidationError(property) != null)
{
return false;
}
}
return true;
}
private string GetValidationError(string property)
{
string error = null;
switch (property)
{
case "CreditCardNumber":
error = ValidateCreditCardNumber();
break;
case "CreditCardSecurityCode":
error = ValidateCreditCardSecurityCode();
break;
case "ExpirationYear":
error = ValidateExpirationYear();
break;
case "ExpirationMonth":
error = ValidateExpirationMonth();
break;
default:
break;
}
return error;
}
private string ValidateExpirationMonth()
{
if (ExpirationYear == DateTime.Now.Year && ExpirationMonth < DateTime.Now.Month)
{
return _localizationService.GetString("/Checkout/Payment/Methods/CreditCard/ValidationErrors/ExpirationMonth");
}
return null;
}
private string ValidateExpirationYear()
{
if (ExpirationYear < DateTime.Now.Year)
{
return _localizationService.GetString("/Checkout/Payment/Methods/CreditCard/ValidationErrors/ExpirationYear");
}
return null;
}
private string ValidateCreditCardSecurityCode()
{
if (string.IsNullOrEmpty(CreditCardSecurityCode))
{
return _localizationService.GetString("/Checkout/Payment/Methods/CreditCard/Empty/CreditCardSecurityCode");
}
if (!Regex.IsMatch(CreditCardSecurityCode, "^[0-9]{3}$"))
{
return _localizationService.GetString("/Checkout/Payment/Methods/CreditCard/ValidationErrors/CreditCardSecurityCode");
}
return null;
}
private string ValidateCreditCardNumber()
{
if (string.IsNullOrEmpty(CreditCardNumber))
{
return _localizationService.GetString("/Checkout/Payment/Methods/CreditCard/Empty/CreditCardNumber");
}
return null;
}
}</pre>
</div>
</div>
</div>
</div>
<p>And finally, we will need IDataErrorInfo for generate message on UI</p>
<div class="clearfix"> </div>
<div id="code5" class="panel-group">
<div class="panel panel-info">
<div id="samplecode5" class="panel-heading"><span class="panel-title small"><a href="#samplecode5a" data-toggle="collapse" data-parent="#code5"> Added code </a></span></div>
<div id="samplecode5a" class="panel-collapse collapse in">
<div class="panel-body">
<pre>public class AuthorizePaymentMethod : PaymentMethodBase, IDataErrorInfo
{
public string Error
{
get { return null; }
}
public string this[string columnName]
{
get { return GetValidationError(columnName); }
}
}</pre>
</div>
</div>
</div>
<div class="panel panel-success">
<div id="fullcode5" class="panel-heading"><span class="panel-title small"><a class="collapsed" href="#fullcode5b" data-toggle="collapse" data-parent="#code5">AuthorizePaymentMethod.cs</a></span></div>
<div id="fullcode5b" class="panel-collapse collapse">
<div class="panel-body">
<pre>public class AuthorizePaymentMethod : PaymentMethodBase, IDataErrorInfo
{
static readonly string[] ValidatedProperties =
{
"CreditCardNumber",
"CreditCardSecurityCode",
"ExpirationYear",
"ExpirationMonth",
};
public AuthorizePaymentMethod()
: this(LocalizationService.Current, ServiceLocator.Current.GetInstance())
{
}
public AuthorizePaymentMethod(LocalizationService localizationService, IOrderGroupFactory orderGroupFactory) : base(localizationService, orderGroupFactory)
{
}
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/CreditCardName")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/CreditCardName")]
public string CreditCardName { get; set; }
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/CreditCardNumber")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/CreditCardNumber")]
public string CreditCardNumber { get; set; }
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/CreditCardSecurityCode")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/CreditCardSecurityCode")]
public string CreditCardSecurityCode { get; set; }
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/ExpirationMonth")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/ExpirationMonth")]
public int ExpirationMonth { get; set; }
[LocalizedDisplay("/Checkout/Payment/Methods/CreditCard/Labels/ExpirationYear")]
[LocalizedRequired("/Checkout/Payment/Methods/CreditCard/Empty/ExpirationYear")]
public int ExpirationYear { get; set; }
public string Error
{
get { return null; }
}
public string this[string columnName]
{
get { return GetValidationError(columnName); }
}
public override IPayment CreatePayment(decimal amount, IOrderGroup orderGroup)
{
var payment = orderGroup.CreateCardPayment(_orderGroupFactory);
payment.CardType = "Credit card";
payment.PaymentMethodId = PaymentMethodId;
payment.PaymentMethodName = "Authorize";
payment.Amount = amount;
payment.CreditCardNumber = CreditCardNumber;
payment.CreditCardSecurityCode = CreditCardSecurityCode;
payment.ExpirationMonth = ExpirationMonth;
payment.ExpirationYear = ExpirationYear;
payment.Status = PaymentStatus.Pending.ToString();
payment.CustomerName = CreditCardName;
payment.TransactionType = TransactionType.Authorization.ToString();
return payment;
}
public override void PostProcess(IPayment payment)
{
var creditCardPayment = (ICreditCardPayment)payment;
var visibleDigits = 4;
var cardNumberLength = creditCardPayment.CreditCardNumber.Length;
creditCardPayment.CreditCardNumber = new string('*', cardNumberLength - visibleDigits)
+ creditCardPayment.CreditCardNumber.Substring(cardNumberLength - visibleDigits, visibleDigits);
}
public override bool ValidateData()
{
foreach (string property in ValidatedProperties)
{
if (GetValidationError(property) != null)
{
return false;
}
}
return true;
}
private string GetValidationError(string property)
{
string error = null;
switch (property)
{
case "CreditCardNumber":
error = ValidateCreditCardNumber();
break;
case "CreditCardSecurityCode":
error = ValidateCreditCardSecurityCode();
break;
case "ExpirationYear":
error = ValidateExpirationYear();
break;
case "ExpirationMonth":
error = ValidateExpirationMonth();
break;
default:
break;
}
return error;
}
private string ValidateExpirationMonth()
{
if (ExpirationYear == DateTime.Now.Year && ExpirationMonth < DateTime.Now.Month)
{
return _localizationService.GetString("/Checkout/Payment/Methods/CreditCard/ValidationErrors/ExpirationMonth");
}
return null;
}
private string ValidateExpirationYear()
{
if (ExpirationYear < DateTime.Now.Year)
{
return _localizationService.GetString("/Checkout/Payment/Methods/CreditCard/ValidationErrors/ExpirationYear");
}
return null;
}
private string ValidateCreditCardSecurityCode()
{
if (string.IsNullOrEmpty(CreditCardSecurityCode))
{
return _localizationService.GetString("/Checkout/Payment/Methods/CreditCard/Empty/CreditCardSecurityCode");
}
if (!Regex.IsMatch(CreditCardSecurityCode, "^[0-9]{3}$"))
{
return _localizationService.GetString("/Checkout/Payment/Methods/CreditCard/ValidationErrors/CreditCardSecurityCode");
}
return null;
}
private string ValidateCreditCardNumber()
{
if (string.IsNullOrEmpty(CreditCardNumber))
{
return _localizationService.GetString("/Checkout/Payment/Methods/CreditCard/Empty/CreditCardNumber");
}
return null;
}
}</pre>
</div>
</div>
</div>
</div>
<h3 id="AuthorizeViewModelcs">Implement AuthorizeViewModel</h3>
<p>AuthorizeViewModel is model for our view</p>
<div class="clearfix"> </div>
<div id="code6" class="panel-group">
<div class="panel panel-success">
<div id="fullcode6" class="panel-heading"><span class="panel-title small"><a class="collapsed" href="#fullcode6b" data-toggle="collapse" data-parent="#code6">AuthorizeViewModel.cs</a></span></div>
<div id="fullcode6b" class="panel-collapse collapse in">
<div class="panel-body">
<pre>public class AuthorizeViewModel : PaymentMethodViewModel
{
public AuthorizeViewModel()
{
InitializeValues();
}
public List Months { get; set; }
public List Years { get; set; }
public void InitializeValues()
{
Months = new List();
Years = new List();
for (var i = 1; i < 13; i++)
{
Months.Add(new SelectListItem
{
Text = i.ToString(CultureInfo.InvariantCulture),
Value = i.ToString(CultureInfo.InvariantCulture)
});
}
for (var i = 0; i < 7; i++)
{
var year = (DateTime.Now.Year + i).ToString(CultureInfo.InvariantCulture);
Years.Add(new SelectListItem
{
Text = year,
Value = year
});
}
}
}</pre>
</div>
</div>
</div>
</div>
<h3 id="PaymentMethodViewModelResolvercs">Adding new payment to PaymentMethodViewModelResolver.cs</h3>
<p>Adding those code below to Resolve paymentMethodName</p>
<div class="clearfix"> </div>
<div id="code7" class="panel-group">
<div class="panel panel-info">
<div id="samplecode7" class="panel-heading"><span class="panel-title small"><a href="#samplecode7a" data-toggle="collapse" data-parent="#code7">Added code</a></span></div>
<div id="samplecode7a" class="panel-collapse collapse in">
<div class="panel-body">
<pre>case "Authorize":
return new AuthorizeViewModel { PaymentMethod = new AuthorizePaymentMethod() };</pre>
</div>
</div>
</div>
<div class="panel panel-success">
<div id="fullcode7" class="panel-heading"><span class="panel-title small"><a class="collapsed" href="#fullcode7b" data-toggle="collapse" data-parent="#code7">PaymentMethodViewModelResolver.cs</a></span></div>
<div id="fullcode7b" class="panel-collapse collapse">
<div class="panel-body">
<pre>public class PaymentMethodViewModelResolver
{
public static IPaymentMethodViewModel Resolve(string paymentMethodName)
{
switch (paymentMethodName)
{
case "CashOnDelivery":
return new CashOnDeliveryViewModel { PaymentMethod = new CashOnDeliveryPaymentMethod() };
case "GenericCreditCard":
return new GenericCreditCardViewModel { PaymentMethod = new GenericCreditCardPaymentMethod() };
case "Authorize":
return new AuthorizeViewModel { PaymentMethod = new AuthorizePaymentMethod() };
}
throw new ArgumentException("No view model has been implemented for the method " + paymentMethodName, "paymentMethodName");
}
}</pre>
</div>
</div>
</div>
</div>
So, we're done with server side. Now, we will add payment view.
<h3 id="paymentview">Adding payment view</h3>
<p>In Views/Checkout folder, add view: _AuthorizePaymentMethod.cshtml.</p>
<p>View _AuthorizePaymentMethod.cshtml was used for inputting card information.</p>
<div class="clearfix"> </div>
<div id="code8" class="panel-group">
<div class="panel panel-success">
<div id="samplecode8" class="panel-heading"><span class="panel-title small"><a href="#samplecode8a" data-toggle="collapse" data-parent="#code8">_AuthorizePaymentMethod.cshtml</a></span></div>
<div id="samplecode8a" class="panel-collapse collapse">
<div class="panel-body">
<pre>@model EPiServer.Reference.Commerce.Site.Features.Payment.ViewModels.AuthorizeViewModel
@Html.HiddenFor(model => model.PaymentMethod.PaymentMethodId)
<div class="form-group">
@Html.LabelFor(model => model.PaymentMethod.CreditCardName)
@Html.TextBoxFor(model => model.PaymentMethod.CreditCardName, new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.PaymentMethod.CreditCardName)
</div>
<div class="row">
<div class="form-group col-md-6">
@Html.LabelFor(model => model.PaymentMethod.CreditCardNumber)
@Html.TextBoxFor(model => model.PaymentMethod.CreditCardNumber, new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.PaymentMethod.CreditCardNumber)
</div>
<div class="form-group col-md-6">
@Html.LabelFor(model => model.PaymentMethod.CreditCardSecurityCode)
@Html.TextBoxFor(model => model.PaymentMethod.CreditCardSecurityCode, new { @class = "form-control", maxlength = "3" })
@Html.ValidationMessageFor(model => model.PaymentMethod.CreditCardSecurityCode)
</div>
</div>
<div class="row">
<div class="form-group col-md-6">
@Html.LabelFor(model => model.PaymentMethod.ExpirationYear)
@Html.DropDownListFor(model => model.PaymentMethod.ExpirationYear, Model.Years, new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.PaymentMethod.ExpirationYear)
</div>
<div class="form-group col-md-6">
@Html.LabelFor(model => model.PaymentMethod.ExpirationMonth)
@Html.DropDownListFor(model => model.PaymentMethod.ExpirationMonth, Model.Months, new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.PaymentMethod.ExpirationMonth)
</div>
</div></pre>
</div>
</div>
</div>
</div>
<p><img class="img-thumbnail" title="Sample_images" alt="Sample_images" src="https://lh3.googleusercontent.com/lYN-h5sycF0AiVUz91LIpxzw1c2pqzrU2S3SOwKGbKGP5kemxdGy5gaecpdG93TtQZbZznwIBwW39-C6_o79HjDqvL3nFFqt5wRQdUo8c8Zzbjr8AoDg2RD-X5zlcAd4UCqffeDrmrECe5ybWQMROtnnOAmtUQ1ig1ZarlZn8Q9EGaF3gE6GSxJ_AGo6wFKu7nnWwrMqaBVA2JBQMhxQHKBS1_615JbddfthN_q11e3wCuAbX4gTzFHjoLIQBLvPJXWozirK3WnyHYve2USXP9a7Gwdo8nJzc3Gfu67a7ZeGqqjuGYVrZQvU7JblUPU-Yo_-2OoMzgVbCUKfNy_zG0jUVHWgAj66PSlFENqDlkawHTrA2XbJdoByCRsYKT474g17B1bjQ5fh0fNhbd_biJ3NPx_TvgVin2VVfITNm4kwacLNzZO_JEHZKhmzNOd-I_lPdQhISBd7RZeWbmCoZhpTJyX9C4Yq1AvqVMcE7QWJWaroVABwG7wMkbPV32T9V8NFb3VQy5zbIwI8VgN_RTKlianzAmOGQuuJyfXM1jgSFALM6Y8NxbwDI-kO9xGqKBhT8c0Edej0CzQYh2otJc5m29SvG-0oC0t44nQAKqFvzSdrXKk-63YOwMNzJ_ji9psgqbikLGPQUIPbOZFvt1C1fEcj0Hd0ytR6BsMuxxg=w571-h365-no" /></p>
<p>In Views/OrderConfirmation folder, add view: _AuthorizeConfirmation.cshtml.</p>
<p>View _AuthorizeConfirmation.cshtml was used for showing card information in purchase order.</p>
<div class="clearfix"> </div>
<div id="codeX" class="panel-group">
<div class="panel panel-success">
<div id="fullcodeX" class="panel-heading"><span class="panel-title small"><a class="collapsed" href="#fullcodeXb" data-toggle="collapse" data-parent="#codeX">_AuthorizeConfirmation.cshtml</a></span></div>
<div id="fullcodeXb" class="panel-collapse collapse">
<div class="panel-body">
<pre>@model EPiServer.Commerce.Order.ICreditCardPayment
<div class="quicksilver-well">
<h4>@Html.Translate("/OrderConfirmation/PaymentDetails")</h4>
<p>
@Html.Translate("/OrderConfirmation/PaymentInfo/CardType"): @Model.CardType<br>
@Html.Translate("/OrderConfirmation/PaymentInfo/Owner"): @Model.CustomerName<br>
@Html.Translate("/OrderConfirmation/PaymentInfo/CardNumber"): ************@Model.CreditCardNumber.Substring(Model.CreditCardNumber.Length - 4)<br>
@Html.Translate("/OrderConfirmation/PaymentInfo/ExpirationDate"): @Model.ExpirationMonth/@Model.ExpirationYear<br>
@Html.Translate("/OrderConfirmation/PaymentInfo/CVV"): ***
</p>
</div></pre>
</div>
</div>
</div>
</div>
<p><img class="img-thumbnail" title="Sample_images" alt="Sample_images" src="https://lh3.googleusercontent.com/zXGWws0vn0pe_svMqyqADcCYt9CQGBCzkOIHams5sYGoohqn_1nY8BfHTMhXipZY5idVVMae5WIrCV1sun-0QKeTM1yATi3fAHoH-LE62pXox4chqII3Q6nWhO1D4qTITKauFKAf3dCcZmOKIChXBVoJ0GkXTwSX6v4e0yAjn4SdRii5E_lkkgE3R4_l9WvpHPyf9QKIO2mdB_6r_jSorWY0veXkJSy61WXf8W6VTtvhFY8DBHx7UztJzMLL_hgrag-jLGNuRjrCsJTMSlVURj7FQG3muYfWruFTYM_6gGccFxBWxUb-8u0RCG2O1r-66DU2HKTPPyJ0QdOyct2fZUm0xo-ksv829tHpT84H0plLmaqsJEhxv6nLysCaOKYqwT5cI-k6dZbMUWr-4weLerTNxYN2tge0dyb9nMhdL3Fo4DO-PTu3zzD36h58EZBdTkHG8f5Ess84jwrGh_WXKRxHgJ1Pd9caloMqKash8k4mQREANPX7ScwVTLUaTHigd_ah9rp6kDCnH1Tw4XXJa-NY479V5ofbpApvUVDX7nRv_03W-N9j7iLv16zuijdj_7EDNDsdTgrelYaw63NjfXeUsHcCy6DzAe_a592uPEKtI5UhWVXW6uynViP1WD6uedqeqXAqDd_xAIh_TEajBK3Rd_40O2pZu6ggl9QXcuI=w392-h208-no" /></p>
<p class="informationBox">Remember that those view name must be _{SystemName}PaymentMethod.cshtml and _{SystemName}Confirmation.cshtml.</p>
<p>So, we finished implement authorize.net payment provider on Quicksilver. Now rebuild project, open Quicksilver, add an item to cart, checkout cart to see how it work.</p>
<p>You could download the code in this post from <a title="Quicksilver Enable Authorize.Net" href="https://github.com/sondn2010/Quicksilver_enableAuthorizeNet" target="_blank">GitHub</a> as well.</p>
<p> </p>
<p>Hope this help you.</p>
<p>/Son Do</p>
</div>Introducing SerializableCart mode/blogs/Son-Do/Dates/2017/1/introduce-serializablecart-mode/2017-01-06T04:03:43.1070000Z<p><span>Since 10.2.0, Episerver Commerce introduced big changes that support cart features: <a href="/link/2b7b481e527c40c1baaa0c224082b886.aspx">SerializableCart</a>.</span></p>
<h2><span>Why?</span></h2>
<p><span>In Commerce 9, we improved catalog performance -- about 10 times faster as I remember :). <br />The purpose of SerializableCart is also performance improvement. SerializableCart mode is ready for bigger sites with more concurrent customers. </span></p>
<h2>How?</h2>
<p>To improve performance, SerializableCart mode stores all cart data in one place with data was serialized as json. All order information (addresses, lineitems, and so forth) is converted to a json string when saving and then deserialized when loading. It reduces a lot of time.</p>
<p>In theory, SerializableCart loads 2 times faster and saves 14 times faster than the legacy cart. An impressive number, isn't it? :)</p>
<h2><span>Using SerializableCart </span></h2>
<ol>
<li>Enable SerializableCart mode.</li>
<li>Convert IShippingGateway to IShippingPlugin using abstraction classes. </li>
<li>Convert IPaymentGateway to IPaymentPlugin using abstraction classes. <em>(Refer to <a href="/link/6bb54cfd9c2b4a939b8a92a4a29c09b4.aspx">post</a> to figure out how to migrate)</em></li>
<li>Navigate to CMS/Admin. In <em>Scheduled Jobs</em>, run <em>Legacy cart migration</em> for migrating legacy Cart to SerializableCart.</li>
<li>Finally, process the new cart mode like my previous <a href="/link/9e0d567601884fb68a280758dd03f9b1.aspx">post</a>.</li>
</ol>
<p class="informationBox">Note that this SerializableCart feature is in beta and does not support creating carts in Commerce Manager. But all purchase order operations in Commerce Manager still work.<br /> The latest Quicksilver on <a href="https://github.com/episerver/Quicksilver">Github</a> is using SerializableCart mode.</p>
<p>The final version will be released soon, in some next release :)</p>
<p>We hope that this change helps you!</p>
<p>/Son Do</p>EPiServer Commerce: updating currency exchange rates automatically/blogs/Son-Do/Dates/2016/12/episerver-commerce-updating-currency-exchange-rates-automatically/2016-12-31T12:16:36.3900000Z<p>Simple exchange rates updater automatically.</p>
<hr id="system-readmore" />
<h1>Why we need update exchange rates automatically</h1>
<p>Currency exchange rates are changed everyday or hourly. This is quite important, our commerce site also need to update them to correct product price, shipping cost, ... more often.</p>
<h1>How can we do</h1>
<p>- Currency exchange rates source: in this sample, I used source from <a href="http://fixer.io/">http://fixer.io/</a> - it's free and simple. You can chose whatever source if you feel trust.</p>
<p>- We have EPiServer schedule job - this is called and run in the background at preset time intervals (for more information, you can go <a href="/link/a3006e0980754a1fa3520ba69f77c24d.aspx" target="_blank">here</a>). So we could write a schedule job to update our task.</p>
<h2>Step by step</h2>
<p><strong>Step 1</strong>: In your project site, create a class, name it as ExchangeRatesUpdateJob.cs.</p>
<p>This class will inherit from ScheduleJobBase.</p>
<hr />
<div id="code1" class="panel-group">
<div class="panel panel-info">
<div id="sampleCode1" class="panel-heading">
<h4 class="panel-title"><a href="#sampleCode1a" data-toggle="collapse" data-parent="#code1"> Added code </a></h4>
</div>
<div id="sampleCode1a" class="panel-collapse collapse in">
<div class="panel-body">
<pre>[ScheduledPlugIn(
DisplayName = "Exchange Rates update job",
Description = "This job update exchange rates.")]
public class ExchangeRatesUpdateJob : ScheduledJobBase
{
public override string Execute()
{
return "Exchange Rate update Job was finished.";
}
}
</pre>
</div>
</div>
</div>
</div>
<p>This class is same as document I mentioned above.</p>
<p><strong>Step 2</strong>: using <a href="/link/55fe410f4f65482cab72fb59d47c7561.aspx?documentId=commerce/9/C599B67C" target="_blank">CurrencyManager</a> to update Exchange rates.</p>
<hr />
<div id="code2" class="panel-group">
<div class="panel panel-info">
<div id="samplecode2" class="panel-heading">
<h4 class="panel-title"><a href="#samplecode2a" data-toggle="collapse" data-parent="#code2"> Added code </a></h4>
</div>
<div id="samplecode2a" class="panel-collapse collapse in">
<div class="panel-body">
<pre>[ScheduledPlugIn(
DisplayName = "Exchange Rates update job",
Description = "This job update exchange rates.")]
public class ExchangeRatesUpdateJob : ScheduledJobBase
{
[NonSerialized]
private static readonly ILogger _log = LogManager.GetLogger(typeof(ExchangeRatesUpdateJob));
public override string Execute()
{
string message = string.Empty;
int records = UpdateExchangeRate(out message);
return $"{records.ToString()} currencies were updated. {message}. Exchange Rate update Job was finished.";
}
private static int UpdateExchangeRate(out string message)
{
message = string.Empty;
_log.Information("Starting to update exchange rate job.");
var processedCurrency = 0;
var dto = CurrencyManager.GetCurrencyDto();
var currencyCodeIdMapping = dto.Currency.ToDictionary(c => c.CurrencyCode, c => c.CurrencyId);
try
{
foreach (var code in currencyCodeIdMapping.Keys)
{
int fromCurrencyId;
currencyCodeIdMapping.TryGetValue(code, out fromCurrencyId);
try
{
var uri = $"http://api.fixer.io/latest?base={code}";
using (var client = new WebClient())
{
var json = client.DownloadString(uri);
dynamic d = JObject.Parse(json);
DateTime exchangeDate = d.date;
foreach (var rate in d.rates)
{
string toCurrencyCode = rate.Name;
int toCurrencyId;
if (currencyCodeIdMapping.TryGetValue(toCurrencyCode, out toCurrencyId))
{
var currencyRate = dto.CurrencyRate.NewCurrencyRateRow();
currencyRate.FromCurrencyId = fromCurrencyId;
currencyRate.ToCurrencyId = toCurrencyId;
currencyRate.EndOfDayRate = currencyRate.AverageRate = rate.Value;
currencyRate.CurrencyRateDate = exchangeDate;
currencyRate.ModifiedDate = DateTime.Now;
dto.CurrencyRate.AddCurrencyRateRow(currencyRate);
currencyRate.AcceptChanges();
}
}
dto.CurrencyRate.AcceptChanges();
}
}
catch (Exception ex)
{
message += "Cannot update currency {code}. ";
_log.Error("Cannot update currency {code}.", ex);
continue;
}
dto.AcceptChanges();
processedCurrency++;
_log.Information($"{code} rates were updated");
}
}
catch (Exception ex)
{
_log.Error("The job could not be completed. ", ex);
}
return processedCurrency;
}
}
</pre>
</div>
</div>
</div>
</div>
<p><strong>Step 3</strong>: rebuild solution, reset your site to add our new Schedule job to system.</p>
<p>Navigate to <em><strong>CMS/Admin</strong> </em>menu, we will see our schedule job in left panel.</p>
<p><img alt="Exchange rates schedule job" src="https://lh3.googleusercontent.com/OL85ciqKluVu1XgFycBbBe6wP13FdxZp6YnY9lBhtZprW5BnGSIFbeFC0GvRYyJoJC06KAyOnGjCMI3rnJ7fOnUNKynW7dJT4sxkfO0Z7yLwbyv30o_0ztGVps_Idq0uHlOb5J-RnHW-s9IicZQfC65E9SfzK6Td-WWjW5DWPdNvWjXwKvKs_a9qLD30QTdPFp6Nf2kwseQUrXTe4Bo-JHPXTBjSE-NeyZaVM0W5L2-rI62p7Ixo6CdZYZaoEQTiE3OL6PFQQAEY6xO0eeP9vhdvdP_p3jyY613xYYQq2oFZvlFlbP5eR1IhynBPeAk7ifaABWQIJ4seYb9sZVMvgKPptr96dfaTVOB3hxy80OmKoknrVtOHNMRMKnkEzrKGXKTgT3yz04NcwXzFG6oI7i_-bakBnVv4APSUX4V2mEwmFHeI5lgRLSURbreJHGAL9r8a2ru3BDh5N0uoov5LR4tJW42lVcfIX2mYQrFx_5czxtOjO9qcmf5es9oMtxdvHrj1Ss_NYrDmN74NNqhJeMQaTIrhj4SsSvU9IhimNmO9CKMoaxzn3CYUL1MN9Z6xx037VJGSwYsBB6w888j-oCC8pgX9qQf4uj-5-Uew95t282xuA87FQn5i3giYKiPMxnadeEUZu2eXKyFFeHfaTTd71ufVcF2lOZtQvJ7Qg7Q=w814-h295-no" /></p>
<p>Click on <em>Start Manually</em> to get first exchange rates.</p>
<p><strong>Step 4</strong>: Set Active = true, in this sample, we will set schedule run everyday in 12.00 am.</p>
<p><img alt="Setting Exchange rates schedule job" src="https://lh3.googleusercontent.com/0mzh_hDOUBt2o0YsAJxBf_rMOXqCLtsNaLtNN0M6abGHXbKLcQ13j8x2NIIyDgl42ta9yHxnyW4uMvqI4oRT4v9rKSHWgcr7aYSVkxxKM-t_NQC-vwLoikhsleQ9XcoNxzjHh0qzD71TSIvGGsTJ63QksZ5uXQ0lo0CYrH5GXmSkPSCyjANeeXgDOLgD6gd_Z6r-Wax7EsZ37OqEBUBH-DnK2pOJ5TkXkvD7LWb0LRMalqvxVIUmHTM6RduCtL8bGpy0NstTLG0Nf6WrVoEwZuv2geZHdwb-AMQOMk2PM7TK2UaOZNLy3DixWmjgRzKHsHt-ao-jv39sKtt05rOEG1bhYfWO1D0nm9YUIvVWcD2Ypk3s_-flthyNl2dhiOHGDYK5SFZQU1QTRF0unEwyB-o-DI1Wn2ruzP14qB9MdzMTaRHHXQOK7xzLw-xJhlv-ddVOZPWUBYz2XV3DeIDUkFjiD0jcHze-VP3-65qFBm_W_QZ-v1iGGi-RVPlWqxTJUQFeu0o2T-xl-0qzYEWtntfTIPWmKaKiwd2n2AOxDz-oPC2hmbyzyveUkwpWe0XWx0GYr_0joR1hDJptjUYUrc1JHfAurKEwS0Ln8eCl7lXiRp-lFfFDZeGlwczlBm0G92AcjCPWduhHrT6GuDQZ2OhG9xUeMT14RPduVPinQ98=w815-h292-no" /></p>
<p>So our job was FINISHED and this job will update exchange rates everyday to our site :)</p>
<p>You could download this file from my <a title="Son Do's github" href="https://github.com/sondn2010/EPiServerCommerce_ExchangeRatesUpdateScheduleJob" target="_blank">github</a>.</p>
<p>Hope this post help your works.</p>
<p>/Son Do</p>[No-workflow] Generating cart using Commerce abstraction/blogs/Son-Do/Dates/2016/12/generate-cart-using-commerce-abstraction/2016-12-27T05:41:33.3230000Z<h2>Why</h2>
<p>- Sometime I need a cart for testing new business logic.</p>
<p>- Sometime I need to create some cart for verify listing.</p>
<p>- And sometime I need to create a lot of cart for checking performance.</p>
<p>- In general, I need sample cart data.</p>
<p>Those’re the reason I create and share this, maybe someone need to data cart like me :)</p>
<p>There are 2 sections in this post:</p>
<p>- Creating a <a href="#createsimplecart">simple cart</a>.</p>
<p>- Creating a tool to generate <a href="#generatecarts">a bundle of carts</a>.</p>
<p>Refer to <a href="/link/c1d451003fac4ccfa7c1a9353378fa67.aspx">http://world.episerver.com/documentation/developer-guides/commerce/orders/order-processing/</a> for more detail.</p>
<h1 id="createsimplecart">Creating a simple cart</h1>
<p>We could create a simple cart with short code and pass through all validator. Creating cart is quite simple like code below:</p>
<hr />
<div id="code" class="panel-group">
<div class="panel panel-success">
<div id="samplecode" class="panel-heading">
<h4 class="panel-title"><a href="#samplecodea" data-toggle="collapse" data-parent="#code"> Added code </a></h4>
</div>
<div id="samplecodea" class="panel-collapse collapse in">
<div class="panel-body">
<pre>var orderRepository = ServiceLocator.Current.GetInstance();
var customerId = Guid.NewGuid();
var cart = orderRepository.LoadOrCreateCart(customerId, "DefaultName");
var lineItem = cart.CreateLineItem("entry code you want to add (SKU-24064191 for example)");
lineItem.Quantity = 1;
lineItem.PlacedPrice = 100;
cart.AddLineItem(lineItem);
var cartLink = orderRepository.Save(cart);</pre>
</div>
</div>
</div>
</div>
<p>Verify on DB or Commerce Manager, we’ll found a cart was created with that code. </p>
<p><strong>Note that</strong> those APIs were in Commerce <a href="/link/76974ad8d2a84c1b989ad0ac453ab663.aspx?versionFilter=10.2.0&packageFilter=EPiServer.Commerce&typeFilter=All" target="_blank">10.2.0</a> and below - this version includes new cart feature, that helps us creating cart more simple than before, with shorter and easier to understanding.</p>
<h2>Validate cart</h2>
<p>Adding line below to your existing code:</p>
<hr />
<div id="code1" class="panel-group">
<div class="panel panel-info">
<div id="sampleCode1" class="panel-heading">
<h4 class="panel-title"><a href="#sampleCode1a" data-toggle="collapse" data-parent="#code1"> Added code </a></h4>
</div>
<div id="sampleCode1a" class="panel-collapse collapse in">
<div class="panel-body">
<pre>cart.ValidateOrRemoveLineItems(null); // Validates the line items and removes from the cart if they are invalid.
cart.UpdatePlacedPriceOrRemoveLineItems(null); // Updates all of the line item placed prices for the specified cart or removes the line item if their is no valid price.
cart.UpdateInventoryOrRemoveLineItems(null); // Updates the inventory for var or removes the line item if no available inventory</pre>
</div>
</div>
</div>
<div class="panel panel-success">
<div id="fullCode1" class="panel-heading">
<h4 class="panel-title"><a class="collapsed" href="#fullCode1b" data-toggle="collapse" data-parent="#code1"> Full code </a></h4>
</div>
<div id="fullCode1b" class="panel-collapse collapse">
<div class="panel-body">
<pre>var orderRepository = ServiceLocator.Current.GetInstance();
var customerId = Guid.NewGuid();
var cart = orderRepository.LoadOrCreateCart(customerId, "DefaultName");
var lineItem = cart.CreateLineItem("entry code you want to add (SKU-24064191 for example)");
lineItem.Quantity = 1;
lineItem.PlacedPrice = 100;
cart.AddLineItem(lineItem);
cart.ValidateOrRemoveLineItems(null); // Validates the line items and removes from the cart if they are invalid.
cart.UpdatePlacedPriceOrRemoveLineItems(null); // Updates all of the line item placed prices for the specified cart or removes the line item if their is no valid price.
cart.UpdateInventoryOrRemoveLineItems(null); // Updates the inventory for var or removes the line item if no available inventory
var cartLink = orderRepository.Save(cart);</pre>
</div>
</div>
</div>
</div>
<p>This is no-workflow process and could be used for replacing Cart validate workflow.</p>
<h2>Cart Prepare - applying promotion</h2>
<p>We could apply promotion discount - those discount were defined in new promotion system.</p>
<hr />
<div id="code2" class="panel-group">
<div class="panel panel-info">
<div id="samplecode2" class="panel-heading">
<h4 class="panel-title"><a href="#samplecode2a" data-toggle="collapse" data-parent="#code2"> Added code </a></h4>
</div>
<div id="samplecode2a" class="panel-collapse collapse in">
<div class="panel-body">
<pre>// Apply promotion
var promotionEngine = ServiceLocator.Current.GetInstance();
var rewardDescriptions = cart.ApplyDiscounts(promotionEngine, new PromotionEngineSettings());</pre>
</div>
</div>
</div>
<div class="panel panel-success">
<div id="fullcode2" class="panel-heading">
<h4 class="panel-title"><a class="collapsed" href="#fullcode2b" data-toggle="collapse" data-parent="#code2"> Full code </a></h4>
</div>
<div id="fullcode2b" class="panel-collapse collapse">
<div class="panel-body">
<pre>var orderRepository = ServiceLocator.Current.GetInstance();
var orderRepository = ServiceLocator.Current.GetInstance();
var customerId = Guid.NewGuid();
var cart = orderRepository.LoadOrCreateCart(customerId, "DefaultName");
var lineItem = cart.CreateLineItem("entry code you want to add (SKU-24064191 for example)");
lineItem.Quantity = 1;
lineItem.PlacedPrice = 100;
cart.AddLineItem(lineItem);
cart.ValidateOrRemoveLineItems(null);
cart.UpdatePlacedPriceOrRemoveLineItems(null);
cart.UpdateInventoryOrRemoveLineItems(null);
// Apply promotion
var promotionEngine = ServiceLocator.Current.GetInstance();
var rewardDescriptions = cart.ApplyDiscounts(promotionEngine, new PromotionEngineSettings());
var cartLink = orderRepository.Save(cart);</pre>
</div>
</div>
</div>
</div>
<p>After those code, the promotions will apply cart and cart will be re-calculated. This could be used for replacing CartPrepare workflow.</p>
<p>So creating cart task was finished after applying promotion.</p>
<p>In this sample, we’re using abstraction to create a cart, we don’t use any workflow to validate lineitem or calculate discount like old way. You could read more information in document I mention before or you could see this on latest Quicksilver sample site.</p>
<h1 id="generatecarts">Creating a bundle of carts</h1>
<p>Next, we focus on creating more than 1 cart - just simple cart – and make an UI to define how many carts, how many shipments for each cart and how many lineitem for each shipment like this:</p>
<p><img alt="creating cart UI" src="https://lh3.googleusercontent.com/QNuXbaeWlqIj6-TIFnmfbHz9QV6HdwRg3tviGSblZb0D7qTiJQIWKdD5aPs3quZx-TaOl1znhk9Lzl-0OjaqV5uh3Ys26itfKIBN2jyGIeuzAs86CBksECLO2upJdWwJXNEvDGq-RfgmRluO6Ih72np-rU35nFrqxW1bMBti3PatSuQTTQ4hINfy1tmilA4NAKyqkW71gzw5FRntNdATOlVHo-TrogGmqge7fVFIwvmv9kRik9d0FS9FZWsMadaXmozkXyErYANT5sB8jfAxVqNTQkAWwVNKd8TsC1U9uZYfbIGN8kcFkbggpTO5_LYvDTX-8g7NeGyRDDRiuqjkrsHycUxj9DD_BUVAdYBSo553-I2ekeYYnRPKvKOmKhu2QYj0j60tcVWm7RldsbFY9HKD6jEywVLqkjf7YebWIDSh-d_vSWTwpdJzSJYhE9ubGGW-rqnXtiqtQ85JUmHEL-hnuRWjDLN0uxGJUY8yzwnk6hlGaTgq-xb5_vC8J-RTIxZjMwvvbJ84gr9B9m5nsdO8ya-uCpw2Z0SQwC-vN8z6AKb8SWNjrmUHZjU0aKbdXmn_8vNIj1v-mvd6u_1Cx5zqUIVALXKHURXG6oy3bim6xJulnv91q50S8QvQVuHpgCBnIoNNJDymC_lI-FHCK87opVeFDzS5xHoCvGfcmhE=w383-h378-no" /></p>
<p> </p>
<p><strong>Step 1</strong>: we use Quicksilver sample site for this example. So first of all, clone or download Quicksilver from: <a href="https://github.com/episerver/Quicksilver">https://github.com/episerver/Quicksilver</a></p>
<p><strong>Step 2:</strong> Creating simple page type and adding controller, UI for this page:</p>
<p>- Creating page type: in <em>Features</em> folder, create a folder <em>Generator</em>. Then adding a class, we name it as GeneratorPage.cs</p>
<pre>[ContentType(DisplayName = "Generator page", GUID = "43000bdf-731c-47d2-ae46-001df01dd678", Description = "", AvailableInEditMode = true)]
public class GeneratorPage : PageData
{
[CultureSpecific]
[Display(
Name = "Generator title",
Description = "Title for the Generator page",
GroupName = SystemTabNames.Content,
Order = 1)]
public virtual string Title { get; set; }
}</pre>
<p>- Adding controller for this page type, name it as GeneratorController.cs</p>
<pre>public class GeneratorController : PageController<GeneratorPage>
{
private IOrderRepository _orderRepository;
const string _defaultName = "Default";
// Those variation codes were generated from our variation content.
private IList<string> _listOfEntryCodes = new List<string> { "SKU-40797394", "SKU-40797396", "SKU-40797399", "SKU-40797402", "SKU-40797405", "SKU-40797409", "SKU-40797413", "SKU-40797417", "SKU-40797420", "SKU-40797423","SKU-40797426","SKU-40707713","SKU-40707735","SKU-40707740","SKU-40707746","SKU-40800952","SKU-40707751","SKU-40707717","SKU-40707721","SKU-40707730","SKU-40801260","SKU-40707703","SKU-41136685","SKU-41136683","SKU-41136682",
"SKU-41136681","SKU-41136684","SKU-41136726","SKU-41136690","SKU-41136688","SKU-41136687","SKU-41136686","SKU-41136689","SKU-42313640","SKU-42313641","SKU-42313642","SKU-42313643","SKU-42313644","SKU-42313635","SKU-42313636","SKU-42313637","SKU-42313638","SKU-42313639","SKU-42313650","SKU-43093280","SKU-43093282","SKU-43093281","SKU-43093283","SKU-43093284","SKU-40835688","SKU-42340084","SKU-42340065","SKU-42340072","SKU-42340074","SKU-42340076",
"SKU-42340078","SKU-42340080","SKU-42340066","SKU-42340067","SKU-42340069","SKU-42340070","SKU-38424528","SKU-38424545","SKU-38424534","SKU-38424537","SKU-38424552","SKU-40707729","SKU-40707701","SKU-40707709","SKU-40707712","SKU-40707716","SKU-40707722","SKU-40707749","SKU-40707754","SKU-40707758","SKU-40707763","SKU-40707768","SKU-40707883","SKU-40707863","SKU-40707865","SKU-40707868","SKU-40707872","SKU-40707876","SKU-40707823","SKU-40707827" };
public GeneratorController(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
public ViewResult Index()
{
return View();
}
[HttpPost]
public ViewResult Index(FormCollection collection)
{
var numberOfCart = string.IsNullOrEmpty(collection["numberOfCart"]) ? 1 : int.Parse(collection["numberOfCart"]);
var numberOfShipment = string.IsNullOrEmpty(collection["numberOfShipment"]) ? 1 : int.Parse(collection["numberOfShipment"]);
var numberOfItem = string.IsNullOrEmpty(collection["numberOfItem"]) ? 1 : int.Parse(collection["numberOfItem"]);
CreateCart(numberOfCart, numberOfShipment, numberOfItem);
ViewBag.Message = $"{numberOfCart} carts were created!";
return View();
}
private void CreateCart(int numberOfCart, int numberOfShipment, int numberOfItem)
{
for (var k = 1; k <= numberOfCart; k++)
{
var cart = _orderRepository.LoadOrCreateCart<ICart>(Guid.NewGuid(), _defaultName);
for (var j = 1; j <= numberOfShipment; j++)
{
var shipment = cart.GetFirstShipment();
if (j > 1)
{
shipment = cart.CreateShipment();
cart.AddShipment(shipment);
}
for (var i = 1; i <= numberOfItem; i++)
{
// Adding random entry to cart
var entryCode = _listOfEntryCodes.OrderBy(s => Guid.NewGuid()).First();
do
{
entryCode = _listOfEntryCodes.OrderBy(s => Guid.NewGuid()).First();
} while (cart.GetAllLineItems().Any(l => l.Code == entryCode));
var lineItem = cart.CreateLineItem(entryCode);
lineItem.Quantity = 1;
lineItem.PlacedPrice = 100;
shipment.LineItems.Add(lineItem);
}
}
_orderRepository.Save(cart);
}
}
}</pre>
<p>- Adding view to this controller (Index.cshtml):</p>
<pre><h1 class="text-center">Cart generator</h1>
@using (Html.BeginForm(null, null, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
<div class="form-group">
<label for="numberOfCart" class="col-sm-3 control-label">Number of cart:</label>
<div class="col-sm-9">
<input type="text" class="form-control text-right" id="numberOfCart" name="numberOfCart" placeholder="Number of cart" value="1">
</div>
</div>
<div class="form-group">
<label for="numberOfShipment" class="col-sm-3 control-label">Number of shipment (each cart)</label>
<div class="col-sm-3">
<input type="text" class="form-control text-right" id="numberOfShipment" name="numberOfShipment" placeholder="Number of shipment for each cart" value="1">
</div>
<label for="numberOfItem" class="col-sm-3 control-label">Number of lineitem (each shipment)</label>
<div class="col-sm-3">
<input type="text" class="form-control text-right" id="numberOfItem" name="numberOfItem" placeholder="Number of lineitem for each shipment" value="2">
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<button type="submit" class="btn btn-primary btn-lg btn-block">Generate</button>
</div>
</div>
}
@if (ViewBag.Message != null)
{
<div class="alert alert-success alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
@ViewBag.Message
</div>
}</pre>
<p><strong>Step 3:</strong> Adding this page to our site</p>
<p><img alt="new page" src="https://lh3.googleusercontent.com/6cLrGZgiIFK0LqrBF_fTSmiO2KrnKli3mhaoymO-PRTAlOO5Z-Ltq25uJgd5gq3ru1XyPHeiP0NFHDZuTNUUTdzzTd9LT9gPQPWHRuRamqJ4I2ITES9fzM5Oju-b4cpJsiSyrqmutmB39I2bG7m--fAghBxG1UkA5KxPpILlz2fuP3YIT8d3kd0BPOhzCYBBMgUCG1QgvMb0XCI3x73sHLDSwrUeyJ3Hov_ckMKpcPQyLw3h7qsK-qxxmugNzKprKOsXBTnDvPlzARSrZmKPlVQeXI1UO1lSVFblxlLg0iTNYL0K1iUjK1a4YF2wWk_DeJL0eJaMFpaxc4knJP6YhbPXGChkX8QiaRzWxhxFO50aye3vAG8Nf1-PB1wtheXKosOA_LuAD3Q5YhWOtV64V5MFx5u9WMBGofFbtz9rRq40Xim1ich2sVz2zhsM2yM1af-oU5WwCXu3WIhhhQZkSWrNPqD9DsV_m_QTKArMpCnl3FS5sSH7Mxmpns_d1YhdtGeqrC9feH-5YZpuTmH5I3j4AVgj5Tc6XFqtRyaMcyw8Yrolvi8iArP2C-WwjbOTqp3aQEEwZfYtVwutCsWGYzCY7-qa-rkPF_aVJOTwGj5oiR0lpuUHPKI2UJX3AxTDtiraqxuVAjyg6tYdrQuXLxWM4hsoA9X56sShFaQBvlM=w829-h355-no" /></p>
<p><img alt="generate cart UI" src="https://lh3.googleusercontent.com/QNuXbaeWlqIj6-TIFnmfbHz9QV6HdwRg3tviGSblZb0D7qTiJQIWKdD5aPs3quZx-TaOl1znhk9Lzl-0OjaqV5uh3Ys26itfKIBN2jyGIeuzAs86CBksECLO2upJdWwJXNEvDGq-RfgmRluO6Ih72np-rU35nFrqxW1bMBti3PatSuQTTQ4hINfy1tmilA4NAKyqkW71gzw5FRntNdATOlVHo-TrogGmqge7fVFIwvmv9kRik9d0FS9FZWsMadaXmozkXyErYANT5sB8jfAxVqNTQkAWwVNKd8TsC1U9uZYfbIGN8kcFkbggpTO5_LYvDTX-8g7NeGyRDDRiuqjkrsHycUxj9DD_BUVAdYBSo553-I2ekeYYnRPKvKOmKhu2QYj0j60tcVWm7RldsbFY9HKD6jEywVLqkjf7YebWIDSh-d_vSWTwpdJzSJYhE9ubGGW-rqnXtiqtQ85JUmHEL-hnuRWjDLN0uxGJUY8yzwnk6hlGaTgq-xb5_vC8J-RTIxZjMwvvbJ84gr9B9m5nsdO8ya-uCpw2Z0SQwC-vN8z6AKb8SWNjrmUHZjU0aKbdXmn_8vNIj1v-mvd6u_1Cx5zqUIVALXKHURXG6oy3bim6xJulnv91q50S8QvQVuHpgCBnIoNNJDymC_lI-FHCK87opVeFDzS5xHoCvGfcmhE=w383-h378-no" /></p>
<p><strong>Step 4:</strong> we need to make sure that our variants have enough in stock quality then we ready to generate a lot of carts :)</p>
<p><img alt="generate cart UI" src="https://lh3.googleusercontent.com/Q_BWMF8Ch5_WZBWm5cLyzhS-25FlJDJUFqYs9QHraP5-NEwT96dmYjepAqFNcW8zLsHCuZVQDe4qIlfxf-MgULbd3oj8pBR1_m803Z4ALA1OxU58O_vBR8ZdGfNUn9YveSKaRR5Kew_L5mTMhs4B-YV4GoDS1htDFdcl1d8ya_Ezx2kUGLqUVqgzZro4hAjGsBOgH_E7n_OqxR2bOi9vKrDNW7E5e0FMvI0VyBWkLFm4KofLfMOwMe27YMHjp-SDX0_iJcNo8bo6_T4TL7UWkvqoHmng-vhZhgIT5g2N0wOOFUfh-wg8oRA8vkiqlWgI4mfy-YxMLbFNSOSK2GIZ2j0lnULtld3c18K97DEkoPSbaZH9IhmER3myuJhfxEqwq3XoiQf22oy_SH4RwozzmwM-bhWyruK0wPrSLHCHwxrhmkjST6eJvLHl79fp7MxFpxb0KLh2RtYcfU0h9ymHfzMGHuDKuIC3im-A_gYz1Ey0q478tqeXiANeXQxePEILnNh4q8ERiVVcJDN7-nvwB8Z7HDRumM1VLC1YQDieLu8HFlGuIeZ5tq46vyX0ubhrAg8t253ebshQ7dbhUaZVRyfPbDi9CDsm97abqaEA1ZhYz_rpQ9DRspIWaGRjpA6OCWBgIslp00yPCjpGHpNG5l4GeQ8Am9RadiDO-tF4Lto=w521-h471-no" /></p>
<p>DONE :)</p>
<p>This sample will help us creating cart data for testing, you could do step by step in this post or get it from <a title="sondn2010's github" href="https://github.com/sondn2010/EPiServerCommerce_samplecode" target="_blank">my github</a>.</p>
<p> </p>
<p>Hope this could help your task.</p>
<p>/Son Do</p>