Using Jabbr chat with MySql database

I wanted to integrate a .net chat application for my Mauritius Chat project and came across Jabbr which has most if not all of the features I wanted. However it works with SQL Server Express by default but I wanted to use MySql instead. The reason for that is that I’ve got a dedicated where I’m running MySql because it’s free compared to SQL Server 2008/2012 and I didn’t want another service for SQL Server Express to run just for one application – I have to use the limited RAM carefully. So this post is all about how I finally managed to do that.

First thing first – you have to download the source code from Jabbr repository on Github, There’s a Download Zip button on the left hand side.

Next you’ll need to download the MySql .Net Connector 6.9.3 and install it. If you have previous versions of the MySql .Net connector, like I did, you’ll have to uninstall that first.

These are the changes you’ll need in your web.config file:

Connection String


<connectionStrings>
<add name="Jabbr" providerName="MySql.Data.MySqlClient" connectionString="Database=Jabbr;Port=3203;Data Source=localhost;User Id=YourUserName;Password=YourPassword;"/>
</connectionStrings>

Entity Framework


<entityFramework codeConfigurationType="MySql.Data.Entity.MySqlEFConfiguration, MySql.Data.Entity.EF6">
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
<providers>
<provider invariantName="MySql.Data.MySqlClient" type="MySql.Data.MySqlClient.MySqlProviderServices, MySql.Data.Entity.EF6" />
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
</providers>
</entityFramework>

You will also need to reference the MySql.Data.Entity.EF6.dll in your project. You will find that where the MySql .Net Connector has been installed and you will have to choose the assembly according to the .net version you’re using, in my case .net 4.5.

Problems I encountered

When you recompile the project, you’d think it will work but for me it didn’t because it kept complaining about this error:

Schema specified is not valid. Errors:
(15,12) : error 2019: Member Mapping specified is not valid. The type ‘Edm.DateTimeOffset[Nullable=False,DefaultValue=,Precision=]’ of member ‘When’ in type ‘JabbR.Models.Attachment’ is not compatible with ‘MySql.timestamp[Nullable=False,DefaultValue=,Precision=0]’ of member ‘When’ in type ‘CodeFirstDatabaseSchema.Attachment’.
(40,12) : error 2019: Member Mapping specified is not valid. The type ‘Edm.DateTimeOffset[Nullable=True,DefaultValue=,Precision=]’ of member ‘RequestPasswordResetValidThrough’ in type ‘JabbR.Models.ChatUser’ is not compatible with ‘MySql.timestamp[Nullable=True,DefaultValue=,Precision=0]’ of member ‘RequestPasswordResetValidThrough’ in type ‘CodeFirstDatabaseSchema.ChatUser’.
(66,12) : error 2019: Member Mapping specified is not valid. The type ‘Edm.DateTimeOffset[Nullable=False,DefaultValue=,Precision=]’ of member ‘When’ in type ‘JabbR.Models.ChatMessage’ is not compatible with ‘MySql.timestamp[Nullable=False,DefaultValue=,Precision=0]’ of member ‘When’ in type ‘CodeFirstDatabaseSchema.ChatMessage’.
(95,12) : error 2019: Member Mapping specified is not valid. The type ‘Edm.DateTimeOffset[Nullable=False,DefaultValue=,Precision=]’ of member ‘LastActivity’ in type ‘JabbR.Models.ChatClient’ is not compatible with ‘MySql.timestamp[Nullable=False,DefaultValue=,Precision=0]’ of member ‘LastActivity’ in type ‘CodeFirstDatabaseSchema.ChatClient’.
(96,12) : error 2019: Member Mapping specified is not valid. The type ‘Edm.DateTimeOffset[Nullable=False,DefaultValue=,Precision=]’ of member ‘LastClientActivity’ in type ‘JabbR.Models.ChatClient’ is not compatible with ‘MySql.timestamp[Nullable=False,DefaultValue=,Precision=0]’ of member ‘LastClientActivity’ in type ‘CodeFirstDatabaseSchema.ChatClient’.

Basically in the Jabbr code, some of the properties (especially for DateTime) are marked as DateTimeOffset which is stores the date/time as an offset of UTC, rather than the server date/time. However the DateTimeOffset type was being mapped to datetime in MySql and Entity Framework realised it was not a proper mapping. I tried to check for solutions online but couldn’t find any and since this was not a big deal for me (storing the date/time as UTC), I ended up replacing the properties marked as DateTimeOffset as DateTime. When you do this, you also have to modify some parts of the code where it uses DateTimeOffset.UtcNow with just DateTime.Now. I’ve used replace/find and there are less than 5 occurrences if I remember correctly.

Okay at this point, the application compiles but when browsing the site, the application tells me that there’s not database/matching tables yet so I would need to use the Package Manager Console and run the command Update-Database. When I did that, it failed at one specific migration. So I tried to see what was happening by issuing a Update-Database -verbose command and I noticed MySql was complaining about a variable was not declared. That specific migration to was to rename a column from Proivder to Provider, something like that. So I thought I could just exclude that particular migration and do the rename manually but the migration still failed at another point.

I therefore decided to delete the whole Migrations folder so that the schema is created from what’s in the code without having to run it any migrations (this is only good if you’re just setting up the website and you don’t have any data yet). I had to comment out the Migration calls in Startup.cs and run the following command in Package Manager Console: Update-Database

I also issued Enable-Migrations so that if I make any changes, then these are tracked.

In the end, I was able to run Jabbr chat with a MySql database. Now I need to create a custom membership provider so that logins details are checked against the Users table which resides in another project, so I might need to create an API for that.

Setting Git on external hard drive or USB flash in Windows

At home, I usually work on personal projects and I wanted an easy way to back up my data and put my code under source control. I decided to go with Git for this and use my 250Gb external hard drive for the remote repository (you can use a USB flash drive as well if you want).

So the first thing to do is download Git Extensions and install the software. I installed mine on the C drive in Windows 7 using the default settings.

The next thing was to let Git know where my local repo for my project is. So I browsed to the folder where my project was located (the directory which contains all the C# source files from Visual Studio) and right click. This gives you the option for “Git Init here” which when chosen creates .git directory marking this folder as your local working directory. Open your Git Extensions program and do a commit to get your codes under source control (locally).

Once that is done, you’ll need to create a remote repository on your external hard drive or USB stick. Just create a folder there eg H:\Git\MyProject and when you right click in the folder, you will be able to click on the “Git Bash” option which will open a command line tool. Type the following command in there:

git init –bare

This will create a bare repository in the current directory.

Go back to Git Extensions and do a push now. It will ask you where to push to and you just need to enter the location of the remote repo you’ve just created and voila, all done.

Notes

Although I was able to set up the local repo using Git Extensions, I was not able to do the same for the remote repo, hence I’ve used the command line in Git Bash.

The local repo contains all your source code (it’s a working copy after all) but the remote one has only git files. My local repo was 8Mb while the remote one was only 0.5Mb so I was worried it was not a proper backup of my data. However that was not the case – I tried to get another working copy from the remote repo just to make sure that if my computer crashed, I would still be able to get all my source codes from the external drive and that worked perfectly. Actually it’s better because the size of the remote repo backup was way less than the working copy.

EPiServer Dynamic Content not Rendering on page

After spending like 4hrs trying to find out why a dynamic property was not being loaded properly on a webpage, I finally stumbled upon a website with such an easy solution. Basically we had a custom dynamic property which was supposed to output some data but EPiServer was not rendering the control properly.

In the source code, I expected to find :

<a style=”” href=”/Video/Vimeo/60255727/” class=”trigger-video” data-trackingid=”&lt;a href=”><span></span>
<img src=”/Global/PersonalCover-Screengrab.jpg” alt=”Personal cover” />
</a>

…but here’s what I saw instead:

<div data-classid=”b30218a7-77fc-43dd-a844-81935aa9b35e” data-dynamicclass=”Video Plugin” data-state=”VgBpAGQAZQBvAEkAZAA=&amp;NgAwADIANQA1ADcAMgA3AA==|VAByAGEAYwBrAGkAbgBnAEkAZAA=&amp;PABhACAAaAByAGUAZgA9ACIAIgAgAG8AbgBjAGwAaQBjAGsAPQAiAF8AZwBhAHEALgBwAHUAcwBoACgAWwAnAF8AdAByAGEAYwBrAEUAdgBlAG4AdAAnACwAIAAnAFYAaQBkAGUAbwAnACwAIAAnAFAAbABhAHkAJwAsACAAJwBQAGUAcgBzAG8AbgBhAGwAQwBvAHYAZQByACcAXQApADsAIgA+AFYAaQBkAGUAbwAgAGwAaQBuAGsAIABoAGUAcgBlADwALwBhAD4A|SQBtAGEAZwBlAFUAcgBsAA==&amp;LwBHAGwAbwBiAGEAbAAvAFAAZQByAHMAbwBuAGEAbABDAG8AdgBlAHIALQBTAGMAcgBlAGUAbgBnAHIAYQBiAC4AagBwAGcA|SQBtAGEAZwBlAEEAbAB0AA==&amp;UABlAHIAcwBvAG4AYQBsACAAYwBvAHYAZQByAA==|QQBsAGkAZwBuAFIAaQBnAGgAdAA=&amp;” data-hash=”jpJE7dVU4KCYCarsF+2bNaNENaNdJwG1niTwbyZc7CY=” contentEditable=”false” class=”epi_dc”><div class=”epi_dc_h”><div class=”epi_dc_l”><div class=”epi_dc_title”>Video Plugin</div></div><div class=”epi_dc_t”><a href=”#” class=”epi_dc_editBtn”>.</a><a href=”#” class=”epi_dc_previewBtn”>.</a></div></div></div>

EPiServer was not rendering the control properly but was just outputting the string.

The fix is really easy. All I had to do was change the way the dynamic content is being output in code.

So instead of this:

<%= CurrentPage.MyDynamicContent %> // wrong

I used this:

<EPiServer:Property runat=”server” PropertyName=”MyDynamicContent” />

Note that this is the way to render dynamic content in EPiServer but there’s a way you can force the literal output as well to work.

Learning EPiServer and Extending Its Functionality

So I’ve recently joined this company and they use EPiServer as a base and build custom functionality on top of that. Therefore I was required to learn how the CMS works in order to fulfill my role as a .NET Web Developer.

The very first thing I needed to do was to get a dev version on my local machine.  On Episerver’s website, I downloaded the version we used most in the company (EPiServer CMS 6 R2) and then I launched the installation process. The setup did not go smoothly as it got aborted because it could not create the database. My Windows credentials were not given the right permission on MS SQL Express 2012 (blame the System Administrator!). Anyway, once that got sorted, I installed the Alloy Tech sample website from EPiServer Deployment Centre. However that was just a blank project with just core files, contrary to what I was expected to see. The thing is I was following a tutorial from a blog and it had screenshots of what the website was supposed to look like but the end result for me was completely different. Later I found out the blog was outdated.

Next it was time getting used to Page Types, something that the CMS uses a lot. Once you get an understanding of that, everything else starts to make more sense. Then comes the querying of the CMS, for example, search for a a list of specific page type starting from the homepage. You can achieve this by creating a criteria query or using PageTypeBuilder (PTB), the latter being more strongly typed. Note that PTB is an open source project which needs to be imported in the project before you can use its awesomeness.

So far so good but still a long way before mastering the CMS; the coding is the easy one 🙂

Fixing the ASP.NET Chart not displaying problem in MVC

To generate charts on the fly, you can leverage the MsChart feature of ASP.Net and it’s a free component which makes it even more cool. Everything was working fine on my development machine which has Windows 7 and using .net framework 4.0 and IIS 7.5. However when I uploaded the files to the production server, the chart was not rendering on the live website and it took me the whole day yesterday (12 hours straight on) to fix the problem. I’ve been on lots and lots of blogs, forums and other websites trying to find a solution to my problem but I had inadvertently created a bug myself. Since you need at least 2 data points to create a chart, I inserted a line of code which would see if there were at least 2 data points before creating the chart, otherwise to return null and that was my biggest mistake. Since many people have reported problems with their asp.net charts not showing up, I find myself obliged to share my experiencing in getting yours fixed.

Which .net framework are you using?

In .net framework 4.0, the charting library has been included. For the 3.5 version, you may be required to have the following dlls in your bin folder:


using System.Web.UI.DataVisualization;
using System.Web.UI.DataVisualization.Charting;
using System.Drawing;

Remember on your dev machine, sometimes those assemblies are already in the GAC but not necessarily on your production servers. It is also worth mentioning the DataVisualization is located in the System.Web.DataVisualization dll (not the omission of UI in the namespace).

Set privateImages to false in your appSettings

In the web.config appSettings key, you might need to turn off privateImages because it is true by default and if the request is not authorised, the images won’t be displayed.

<add key="ChartImageHandler" value="storage=memory;deleteAfterServicing=true;privateImages=false;timeout=20;"/>

Explicitly define the ChartImg.axd http handlers in your web.config

The code below shows the main sections where the configurations need to be added:

<appSettings>
<add key="ChartImageHandler" value="storage=memory;deleteAfterServicing=true;privateImages=false;timeout=20;"/>
 </appSettings>

 <system.web>
 <compilation debug="true" targetFramework="4.0">
 <assemblies>
 <add assembly="System.Web.DataVisualization, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
 </assemblies>
 </compilation>

 <httpHandlers>
 <add path="ChartImg.axd" verb="GET,HEAD,POST" type="System.Web.UI.DataVisualization.Charting.ChartHttpHandler, System.Web.DataVisualization, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false" />
 </httpHandlers>

 <controls>
 <add tagPrefix="asp" namespace="System.Web.UI.DataVisualization.Charting"
 assembly="System.Web.DataVisualization, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
 </controls>
 </system.web>

 <system.webServer>
 <validation validateIntegratedModeConfiguration="false" />
 <handlers>
 <remove name="ChartImageHandler" />
 <add name="ChartImageHandler" preCondition="integratedMode" verb="GET,HEAD,POST" path="ChartImg.axd" type="System.Web.UI.DataVisualization.Charting.ChartHttpHandler, System.Web.DataVisualization, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
 </handlers>
 <system.webServer>

Ignore routes to the chart image handler in your MVC

The MVC routing system may be messing up with your chart image handler sometimes. Many people have had success by ignoring the routes to the url where the charts are created, eg:

routes.IgnoreRoute("ChartImg.axd/{*pathInfo}");
routes.IgnoreRoute("{controller}/ChartImg.axd/{*pathInfo}");
routes.IgnoreRoute("{controller}/{action}/ChartImg.axd/{*pathInfo}");

How to use MsChart in your MVC application

In your view, you just need a an image tag with a url to your controller method which generates the chart as follows:

<img src="/chart/generate/1" alt="chart" />

And in your ChartController:

public FileContentResult Generate(int? id)
 {

// Repository.GetData(id);

List<int> gisData = new List<int>();
 gisData.Add(80);
 gisData.Add(50);
 gisData.Add(95);
 gisData.Add(67);
 gisData.Add(88);

var chart = new Chart();
 chart.Width = 800;

var area = new ChartArea();
 area.AxisY.Maximum = 100;
 area.AxisX.Title = "Attempts";
 area.AxisY.Title = "Points";
 chart.ChartAreas.Add(area);

// create and customize your data series.
 var series = new Series();
 int count = 0;
 foreach (int item in gisData)
 {
 count++;
 series.Points.AddXY(count, item);
 }
 series.Font = new Font("Segoe UI", 8.0f, FontStyle.Bold);
 series.ChartType = SeriesChartType.Line;
 series["PieLabelStyle"] = "Outside";

chart.Series.Add(series);

using (MemoryStream ms = new MemoryStream())
 {
 chart.SaveImage(ms, ChartImageFormat.Png);
 ms.Seek(0, SeekOrigin.Begin);
 return File(ms.ToArray(), "image/png", "mycharg.png");
 }
 }

Instead of an ActionResult for the controller, I’m using a FileContentResult. You can also pass in an id which you can use to query your respository (database) for values to use as data points. In my example, I’m just hard coding the values.

Conclusion

It’s been a really long and frustrating day for me but I’m glad I managed to fix my problem which had nothing to do with the problems other people have faced. Hopefully this post is going to help someone and save their time!

Adding the ASP.net Web API to a Web Forms website

The latest version of the MVC framework comes with ASP.NET Web API support and that’s great. But if like me, you’ve got a web forms website where you want to leverage the capabilities of the Microsoft Web Api, then there are some manual things you need to do.

The first question you need to ask yourself is why do you want the Web Api kit in the first place. For me, my main website runs on web form and it’s too much work at the moment to migrate it to MVC. Other new projects are being done in MVC and I need to way to communicate with the new website. For example, I’ve got a central database which stores user details and I need to get information on a particular user by querying the main website through a web service.  You could very well get the same information by querying the database directly but what if you had business logic as well which filters data before it is displayed? For my problem, I needed to get the total points of a particular user by sending off the UserId.

If you right click on your ASP.NET website in Solution Explorer (Visual Studio) and click on “Add New Item”, you can select the “Web API Controller Class” which I’ve renamed as UserController:

user controller

I didn’t need all those things in the controller, so I removed all and now it looks like this:


public class UserController : ApiController
{
 // GET api/<controller>/5
 public int Get(int id)
 {
 ISession session = NHibernateSessionManager.CreateSession();
 User user = new Repository<User>(session).GetById(id);
 NHibernateSessionManager.CloseSession(session);

return user.Points;
 }
}

But before you could actually compile your site, you need to add the following codes to your Global.ascx file:


void Application_Start(object sender, EventArgs e)
 {
 RouteTable.Routes.MapHttpRoute(
 name: "DefaultApi",
 routeTemplate: "api/{controller}/{id}",
 defaults: new { id = System.Web.Http.RouteParameter.Optional }
 );
 }

This means that if your website is http://www.mydomain.com, you will be able to access your web api at http://www.mydomain.com/api and in my case for my controller it would be:

http://www.mydomain.com/api/user/5 where 5 is the UserId of the user I want to retrieve the points for.

It was as simple as this and I couldn’t believe it.

How to call the Web API url in code

In order to consume the web api, you can use the HttpClient class. For example in my MVC project, I added the following method:


int _points;

public void GetUserPoints(int userId)
 {
 try
 {
 HttpClient client = new HttpClient();
 client.BaseAddress = new Uri(System.Configuration.ConfigurationManager.AppSettings["ApiUrl"]);

client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

HttpResponseMessage response = client.GetAsync(String.Concat("user/", userId)).Result; // Blocking call!
 if (response.IsSuccessStatusCode)
 {
 // Parse the response body. Blocking!
 _points = response.Content.ReadAsAsync<int>().Result;
 }
 }
 catch (Exception ex)
 {
 _points = 50;
 }
 }

You’ll need the following namespaces for it to work:

  • using System.Net.Http;
  • using System.Net.Http.Headers;

Problem getting the Web Api to work on the live server

After everything was tested, I uploaded the new asp.net website which contained my web api interface onto my Windows 2008 box and I was horrified when it threw an error. It was something to do with a missing dll. It happened because the DLL was not found in the GAC while on my local machine that was the case. There are many ways to solve the problem but for me the easier thing was to install the MVC 4 framework on the Windows server to rectify the problem. And that worked!

Error 404 for woff file extension

On one of my website, I was using a special font, the Web Open Font Format (.woff) and while all was working fine in all browsers, I noticed Google Chrome was returning a 404 HTTP Status Code. This meant that it was not able to find the file. I looked into the path given by the style sheet and there it was. So no problem there but what was actually causing the error?

It was my server not knowing what to do with this extension. I was running IIS7.5 and it was not configured to process .woff files. Therefore you either need to add this MIME type to your server by specifying application/x-font-woff to be used for that extension.

woff to iis

Or you can just insert the following lines in your web.config file:


<system.webServer>
 <staticContent>
  <mimeMap fileExtension=".woff" mimeType="application/x-font-woff" />
 </staticContent>
</system.webServer>

I opted for the second option (web.config modification) and Chrome stopped complaining about the missing .woff files.

Fixing Adsense stats not showing up in Google Analytics

I used to be highly involved in optimising Adsense on my sites but when Google Panda struck, that was the last thing on my mind. Before I was getting an increasing amount of visitors every month and there was a steady income coming in but since April 2011, things have changed. I’m not going into the details of this  but a week ago, I decided I must find out what webpages were earning me the most on my sites.

My Adsense account has always been linked to my Analytics account since that functionality was made available but for some reason, only my main domain was being reported in. I double checked my settings in both Adsense & Analytics and they were correct. So I was confused. The report showed only partial earnings and when I investigated, I found statistics for one sub domain was missing.

It was then that I realised the problem was due to a modification I made to my GA code when I wanted to track multiple sub domains with the same code. This is the extra line I had:

_gaq.push([‘_setDomainName’, ‘mydomain.com’]);

To rectify the problem, I had to add the following line to my adsense code:

google_analytics_domain_name=”mydomain.com”;

Therefore your adsense block will look like this:


<script type="text/javascript"><!--
google_analytics_domain_name="mydomain.com";
google_ad_client = "ca-pub-123456789";
/* Custom Adsense Unit */
google_ad_slot = "1000000";
google_ad_width = 336;
google_ad_height = 280;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>

Analytics is now displaying the proper stats for Adsense and I can now see which URLs are bringing in the more clicks and how much the clicks are worth. This will help me optimise these webpages fully and also give me the right insights to monetise other pages of my site.

Note that the Adsense programme does not allow you to modify the ad codes. Therefore you might need to have a separate javascript call as follows:

<script type=”text/javascript”>
google_analytics_domain_name=”mydomain.com”;
</script>

How to pass parameters to the ViewModel in MVC

On a user account page, I had to display specific information regarding the logged in user. Things like Name, Age etc. I designed a ViewModel called UserAccount which takes in a UserId as a parameter to its contructor and based on that value, the VM pulls information from the database for that specific user. Something like this:


public class UserAccount

{

public string Name { get; set;}

public int Age { get; set; }

public UserAccount(int userId)

{

User user = GetUserFromRepository(userId);

Name = user.Name;

Age = user.Age;

}

}

All information I needed was provided by the VM but when it came to attaching this VM to the View, I got stuck. I need to mention I’m quite new to MVC but then I managed to find the solution to this:


public ActionResult MyAccount()
 {
 if (!Helper.IsUserLoggedIn())
 {
 return Redirect(Helper.GetLoginPageWithRedirect());
 }
 else
 {
 var vm = new Core.ViewModel.UserAccount(Helper.GetUserIdFromSession());

return View(vm);
 }
 }

What this does is first check whether the user is actually logged in. If not, it redirects the user to the login page because they should not  be able to access the account page without being signed in. Since you can actually pass in an object when returning a View, you can already initialise your object through constructor injection and then do “return View(YourAlreadyInitialisedObject”);” That saved me from writing extra lines of codes and it works brillantly.

Integrating Paypal button with IPN and PDT in ASP.net or MVC website

I was working on a project where a subscription is needed to access parts of the website (drivingtest.cleverdodo.mu). The idea was to have a Paypal button which would allow users to pay for 1 month subscription at a time and give them instant access. The standard button (the one you get from Paypal’s website) allows you to receive money but you’d have to manually check the payment and update the customer’s record to grant him access. If you wanted your system to be notified instantly, you’ll have to dive into Instant Payment Notification (IPN) and Payment Data Transfer (PDT).

Before I show you the C# codes for Paypal integration, you’ll need to understand what IPN and PDT are. IPN is a message service; when someone has made a payment to you, Paypal will send this message to your listener. It is the most reliable way to know whether you truly have received money because the message will be sent at some point or the other, whether the user does not return to your website after completing the payment or he loses internet connection or there’s congestion and the transaction notification is delayed. However you’ll need to have a listener to capture the message. PDT on the other hand happens when the user either clicks on the “Goc back to merchant website” link (your return url) or when the user is redirected to your website immediately after purchase. If for some reason or the other, the customer does not return to your website, you will receive no notification of a successful payment.

Therefore it is important that you have both IPN and PDT but the good news is that the codes for the two are not that much different after all.

Paypal Sandbox and the Developer account

You do not want to be testing against your live business/personal account. Therefore you’ll need to register for a developer account first to get access to Paypal Sandbox which allows you to test at your heart’s will.

Developer : https://developer.paypal.com/

Once registered, just create a test business account and a personal account so that you can check whether when you buy from the personal account, the money is transferred to your business account.

You’ll also need to sign in with your test business account to https://www.sandbox.paypal.com and go to Profile -> Website Payment Preferences and turn Auto Return and Payment Data Transfer to On so that you get an Identity Token to use for PDT. I had trouble finding my token because I was looking on the Developers url rather than the Sandbox one.

How it works in my project

When a user is logged in, he’s able to see a Paypal button in his dashboard (my account area). When he clicks the button, it takes him to an intermediate page which contains a form with all the necessary values to send off to Paypal. When this page is loaded, a record is created in the order table and the order id is inserted as a hidden form field. I need this value back from Paypal to update the corresponding user subscription record.

So this is an MVC view but the core principle remains the same for any other programming language


@{
 ViewBag.Title = "Paypal Checkout";
}

@section TestHeadContent {
<meta name="robots" content="noindex" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
<script src="/scripts/jquery.countdown.min.js"></script>
<script type="text/javascript">
 $(document).ready(function () {
 $('#paypal-timer').countdown({ until: '+2s', format: 'MS', layout: '{sn}', onExpiry: SubmitTheForm });
 });

function SubmitTheForm() {
 $('#paypalform').submit();
 }
</script>
}

<div id="area">
<div style="min-height:400px;">
<strong>Redirecting you to the Paypal website in <span id="paypal-timer"></span> second(s)...</strong>
<form name="paypalform" id="paypalform" action="@ViewData["PaypalUrl"]" method="post" enctype="application/x-www-form-urlencoded">
 <input type="hidden" name="cmd" value="_xclick" />
 <input type="hidden" name="business" value="@System.Configuration.ConfigurationManager.AppSettings["Business"]" />
 <input type="hidden" name="amount" value="4" />
 <input type="hidden" name="currency_code" value="USD" />
 <input type="hidden" name="notify_url" value="@String.Concat(System.Configuration.ConfigurationManager.AppSettings["WebsiteUrl"], "paypal/ipn")" />
 <input type="hidden" name="cancel" value="@String.Concat(System.Configuration.ConfigurationManager.AppSettings["WebsiteUrl"], "paypal/cancel")" />
 <input type="hidden" name="return" value="@String.Concat(System.Configuration.ConfigurationManager.AppSettings["WebsiteUrl"], "paypal/pdt")" />
 <input type="hidden" name="item_name" value="1 Month Access" />
 <input type="hidden" name="custom" value="@ViewData["OrderId"]" />
</form>
</div>
</div>

The form is populated with values from the AppSettings; Business is your business email address and notify_url is the URL of the IPN listener. The form action is set to either the sandbox or live Paypal url depending on a setting in the web.config file. After 2 seconds, the form is automatically submitted.

Note : You cannot issue a post command from code behind (ie programmatically) because you will need to redirect the user to that same page afterwards for him to enter his details. Therefore you will lose your post data. That is why we’re using a form to post the values and send the users to Paypal.

Code for IPN

Usually this code will go into your business logic layer (thin controllers, fat models) but this is just to simplify things.


public ActionResult IPN()
 {
 log.Info("IPN listener invoked");

try
 {
 var formVals = new Dictionary<string, string>();
 formVals.Add("cmd", "_notify-validate");

string response = GetPayPalResponse(formVals);

if (response == "VERIFIED")
 {
 string transactionId = Request["txn_id"];
 string sAmountPaid = Request["mc_gross"];
 string orderId = Request["custom"];

//_logger.Info("IPN Verified for order " + orderID);

//validate the order
 Decimal amountPaid = 0;
 Decimal.TryParse(sAmountPaid, out amountPaid);

Order order = new Gis.Quiz.Core.Repository.Repository<Order>(_session).GetById(Convert.ToInt32(orderId));

// check these first
 bool verified = true;

string businessEmail = HttpUtility.UrlDecode(Request["business"]);
 if (String.Compare(businessEmail, System.Configuration.ConfigurationManager.AppSettings["Business"], true) != 0)
 verified = false;

string currencyCode = HttpUtility.UrlDecode(Request["mc_currency"]);
 if (String.Compare(currencyCode, "USD", true) != 0)
 verified = false;

string paymentStatus = HttpUtility.UrlDecode(Request["payment_status"]);
 if (String.Compare(paymentStatus, "Completed", true) != 0)
 verified = false;

if (!AmountPaidIsValid(order, amountPaid))
 verified = false;

log.Info("Business : " + businessEmail);
 log.Info("currency : " + currencyCode);
 log.Info("payment status : " + paymentStatus);
 log.Info("amount valid : " + AmountPaidIsValid(order, amountPaid).ToString());

// process the transaction
 if (verified)
 {
 log.Info("Trying to process ipn transaction...");

// check that we have not already processed this transaction
 if (String.Compare(order.TransactionId, transactionId, true) != 0)
 {
 order.Firstname = HttpUtility.UrlDecode(Request["first_name"]);
 order.Lastname = HttpUtility.UrlDecode(Request["last_name"]);
 order.Email = HttpUtility.UrlDecode(Request["payer_email"]);
 order.Street = HttpUtility.UrlDecode(Request["address_street"]);
 order.City = HttpUtility.UrlDecode(Request["address_city"]);
 order.State = HttpUtility.UrlDecode(Request["address_state"]);
 order.Country = HttpUtility.UrlDecode(Request["address_country"]);
 order.Zip = HttpUtility.UrlDecode(Request["address_zip"]);
 order.TransactionId = transactionId;
 order.StatusId = 1;
 order.UpdateDate = Gis.Quiz.Core.Utilities.Helper.FormatNHibernateDateTime(DateTime.Now);

UserSubscription us = new Gis.Quiz.Core.Repository.Repository<UserSubscription>(_session).GetById(order.UserId);
 if (us == null)
 {
 // create new subscription record
 us = new UserSubscription();
 us.Id = order.UserId;
 us.CreateDate = Gis.Quiz.Core.Utilities.Helper.FormatNHibernateDateTime(DateTime.Now);
 us.UpdateDate = Gis.Quiz.Core.Utilities.Helper.FormatNHibernateDateTime(DateTime.Now);
 us.SubscriptionEndDate = Gis.Quiz.Core.Utilities.Helper.FormatNHibernateDateTime(DateTime.Now.AddDays(30));
 }
 else
 {
 // update subscription record
 us.UpdateDate = Gis.Quiz.Core.Utilities.Helper.FormatNHibernateDateTime(DateTime.Now);
 us.SubscriptionEndDate = Gis.Quiz.Core.Utilities.Helper.FormatNHibernateDateTime(DateTime.Now.AddDays(30));
 }

using (ITransaction tx = _session.BeginTransaction())
 {
 _session.Save(order); // save order
 _session.Save(us); // save subscription

tx.Commit();
 }
 }
 }
 else
 {
 //let fail - this is the IPN so there is no viewer
 log.Info("ipn is not valid...");
 }
 }
 else
 log.Info("ipn not verified...");
 }
 catch (Exception ex)
 {
 log.Error("Error caught in IPN : " + ex.ToString());
 }

return View();
 }

You need to check you haven’t processed the transaction yet because you could end up doing it twice as Paypal could send you a notification through both PDT and IPN.

The PDT Code


public ActionResult PDT()
 {
 log.Info("PDT called...");

try
 {
 //_logger.Info("PDT Invoked");
 string transactionId = Request.QueryString["tx"];
 string sAmountPaid = Request.QueryString["amt"];
 string orderId = Request.QueryString["cm"];

Dictionary<string, string> formVals = new Dictionary<string, string>();
 formVals.Add("cmd", "_notify-synch");
 formVals.Add("at", System.Configuration.ConfigurationManager.AppSettings["PayPalPDTToken"]);
 formVals.Add("tx", transactionId);

string response = GetPayPalResponse(formVals);
 //_logger.Info("PDT Response received: " + response);
 if (response.StartsWith("SUCCESS"))
 {
 //_logger.Info("PDT Response received for order " + orderID);

//validate the order
 Decimal amountPaid = 0;
 Decimal.TryParse(sAmountPaid, out amountPaid);

Order order = new Gis.Quiz.Core.Repository.Repository<Order>(_session).GetById(Convert.ToInt32(orderId));

//check the amount paid
 if (AmountPaidIsValid(order, amountPaid))
 {
 // check that we have not already processed this transaction
 if (String.Compare(order.TransactionId, transactionId, true) != 0)
 {
 order.Firstname = GetPDTValue(response, "first_name");
 order.Lastname = GetPDTValue(response, "last_name");
 order.Email = GetPDTValue(response, "payer_email");
 order.Street = GetPDTValue(response, "address_street");
 order.City = GetPDTValue(response, "address_city");
 order.State = GetPDTValue(response, "address_state");
 order.Country = GetPDTValue(response, "address_country");
 order.Zip = GetPDTValue(response, "address_zip");
 order.TransactionId = transactionId;
 order.StatusId = 1;
 order.UpdateDate = Gis.Quiz.Core.Utilities.Helper.FormatNHibernateDateTime(DateTime.Now);

UserSubscription us = new Gis.Quiz.Core.Repository.Repository<UserSubscription>(_session).GetById(order.UserId);
 if (us == null)
 {
 // create new subscription record
 us = new UserSubscription();
 us.Id = order.UserId;
 us.CreateDate = Gis.Quiz.Core.Utilities.Helper.FormatNHibernateDateTime(DateTime.Now);
 us.UpdateDate = Gis.Quiz.Core.Utilities.Helper.FormatNHibernateDateTime(DateTime.Now);
 us.SubscriptionEndDate = Gis.Quiz.Core.Utilities.Helper.FormatNHibernateDateTime(DateTime.Now.AddDays(30));
 }
 else
 {
 // update subscription record
 us.UpdateDate = Gis.Quiz.Core.Utilities.Helper.FormatNHibernateDateTime(DateTime.Now);
 us.SubscriptionEndDate = Gis.Quiz.Core.Utilities.Helper.FormatNHibernateDateTime(DateTime.Now.AddDays(30));
 }

using (ITransaction tx = _session.BeginTransaction())
 {
 _session.Save(order); // save order
 _session.Save(us); // save subscription

tx.Commit();
 }

ViewData["fb"] = "Thank you for your payment. Your transaction has been completed and a receipt for your purchase has been emailed to you. You can now access the paid members area of our website by <a href=\"/myaccount\">clicking here</a>.";
 }
 else
 {
 ViewData["fb"] = "This order has already been processed.";
 }
 }
 else
 {
 //Payment amount is off, this can happen if you have a Gift cert at PayPal, be careful of this!
 ViewData["fb"] = "Payment amount received from paypal does not match order total.";
 }
 }
 else
 {
 ViewData["fb"] = "Your payment was not successful with PayPal";
 }
 }
 catch (Exception ex)
 {
 log.Error("Error caught in PDT : " + ex.ToString());
 }

return View();
 }

PayPalPDTToken is the Identity Token you got in an earlier step above.

The helper methods


/// <summary>
 /// Utility method for handling PayPal Responses
 /// </summary>
 string GetPayPalResponse(Dictionary<string, string> formVals)
 {
 bool useSandbox = Convert.ToBoolean(System.Configuration.ConfigurationManager.AppSettings["IsLocal"]);

string paypalUrl = useSandbox ? "https://www.sandbox.paypal.com/cgi-bin/webscr"
 : "https://www.paypal.com/cgi-bin/webscr";

 HttpWebRequest req = (HttpWebRequest)WebRequest.Create(paypalUrl);

// Set values for the request back
 req.Method = "POST";
 req.ContentType = "application/x-www-form-urlencoded";

byte[] param = Request.BinaryRead(Request.ContentLength);
 string strRequest = Encoding.ASCII.GetString(param);

StringBuilder sb = new StringBuilder();
 sb.Append(strRequest);

foreach (string key in formVals.Keys)
 {
 sb.AppendFormat("&{0}={1}", key, formVals[key]);
 }
 strRequest += sb.ToString();
 req.ContentLength = strRequest.Length;

//for proxy
 //WebProxy proxy = new WebProxy(new Uri("http://urlort#");
 //req.Proxy = proxy;
 //Send the request to PayPal and get the response
 string response = "";
 using (StreamWriter streamOut = new StreamWriter(req.GetRequestStream(), System.Text.Encoding.ASCII))
 {

streamOut.Write(strRequest);
 streamOut.Close();
 using (StreamReader streamIn = new StreamReader(req.GetResponse().GetResponseStream()))
 {
 response = streamIn.ReadToEnd();
 }
 }

return response;
 }

bool AmountPaidIsValid(Order order, decimal amountPaid)
 {
 bool result = true;

if (order != null)
 {
 if (order.Total > amountPaid)
 {
 //_logger.Warn("Invalid order amount to PDT/IPN: " + order.ID + "; Actual: " + amountPaid.ToString("C") + "; Should be: " + order.Total.ToString("C") + "user IP is " + Request.UserHostAddress);
 result = false;
 }
 }
 else
 {
 //_logger.Warn("Invalid order ID passed to PDT/IPN; user IP is " + Request.UserHostAddress);
 }
 return result;
 }

string GetPDTValue(string pdt, string key)
 {
 string[] keys = pdt.Split('\n');
 string thisVal = "";
 string thisKey = "";
 foreach (string s in keys)
 {
 string[] bits = s.Split('=');
 if (bits.Length > 1)
 {
 thisVal = HttpUtility.UrlDecode(bits[1]); // values are urlencoded, not htmlencoded
 thisKey = bits[0];
 if (thisKey.Equals(key, StringComparison.InvariantCultureIgnoreCase))
 break;
 }
 }
 return thisVal;
 }

Important things to remember

Here are some thing worth noting:

  • You need to be logged into your sandbox account on the same browser that you’re sending off the request to Paypal to make a sucessful transaction in testing mode.
  • You can only test IPN on a URL that’s publicly accessible. So localhost will not work.

Paypal integration is not complicated but I had to read lots on different forums/blogs and StackOverflow and I’m grateful to Kona.Web from which I’ve borrowed a lot of code.