Inspired by pain, driven by will and created with hope

She tells me to get a tattoo of her name but doesn’t realise her name is already engraved on my heart.
She believes it’s all over, however what awaits is still togetherness.
She thinks what was, isn’t anymore, yet what remains, is stronger than before.
She sees what everyone else does, although what truly is stays unbeknown to anyone.
She knows what we have is real, yet doubts what cannot be disputed.
She pretends not to love but her heartbeats betrays her at each moment.
She looks for sincerity but doesn’t see the mirror of my eyes.
She tries not to look back but what lies ahead is the dream we built.
She forgets what she needs but attends to my desires.
She finds my excuses lame yet wishes they are true.
She cries in silence and thinks I don’t see her tears.
She fights her conscience but surrenders to fate.
She awaits my call yet pretends to be busy.
She puts on a fake smille and hopes no one catches her.
She builds a wall with her pride but secretly wishes my love to invade.
She wishes to hold my hand yet is scared of my touch.
She feels my pain but refuses to show empathy.
She knocks on my door yet feels surprised to see me.
She hates thinking about me but secretly cherishes my memories.
She listens to her mind but none to her heart.
She loves me but denies it everytime.
She is all that yet is incomplete without me!

Search Engine Optimisation for Mauritian websites

I actively monitor the SERPs on Google.mu for a lot of keywords and I can tell you that people in Mauritius do not invest in SEO at all. Many websites which occupy the top positions rank purely because of lack of competition. I can confidently say that search engine optimisation in Mauritius is non-existant except for a few people who are in the website creation business (I can count them on my hand btw). It is to be noted that an awful lot of businesses do not have a website in this day and age and they couldn’t care any less. You will sometimes be amazed by those who have an online presence because their websites are just not worth the effort. Take for example a popular furniture and electrical giant like Courts which have a single page website with an image flyer that links to their Facebook page. Customers want to see what products you have and what the prices are, not being forced to visit your fan page to see e-catalogues of your products. What, are you still in the 1990s era?

courts

There’s this driving school which has a website (maybe the only one in this business actually) and although it has some useful webpages with great information, the title of all its webpages is just the name of the driving school itself. Anyone in the search engine optimisation business can tell you the title tag is the single most important bit you need to optimise. However this site ranks for a lot of associated keywords because Google is inferring its quality from the url of the webpage and the content it has as a substitution for not having enough relevant websites to display. What I’m trying to say is due to the lack of info on this subject in the Mauritian context, the website is ranking despite being unoptimised – same as the one eye man is king in the valley of the blind.

We also have some individuals who think they know a lot of SEO and use black hat techniques to accomplish their goals. For example, there was this web design firm which managed to get a number 1 listing for a 2 keyword phrase I’m currently tracking but it was short lived because all the external backlinks came from blog comments on sites where there’s no moderation, which has led to the domains being listed as spam. Now you wouldn’t want to associate yourself with bad domains because you’d be considered a spammer too and that will definitely hurt your rankings. Anyway if you’re after rankings that last (or at least factors which have a strong impact on your rankings), you need to go white hat and invest more into branding.

Doing business online is still very new in Mauritius and now is the time to crush your competitors and set yourself apart and gain that trust which will benefit you so much more in the future. And if you’re planning to do SEO, then do it right through ethical ways or don’t do it at all.

A not so obvious 403 error permission denied in IIS 7.5

What could be worse than having your live website inaccessible for a long time? Yesterday, I went onto live chat with my web host because I couldn’t get something working. I wanted to block an IP address that was causing a lot of errors and 404s on my website. I’ve added an entry in the IP and Domain Restrictions Settings in IIS the day before assuming the web server will block all requests to any pages on my sites. Well the following day, I got a few more email alerts regarding more 404 errors through email and it came from the same IP I have banned.

ip address restrictions

So I talked to someone from the web hosting company but she seemed clueless. I figured out she was a Level 1 customer support and had little knowledge, so I left the chat but I was unaware she would mess things up completely for me. After an hour, when I was checking the real time data on Google Analytics, I was surprised to see no visitors online. This has never happened before and I waited for 5 mins but nothing changed. So I tried to access my website to see if there was a problem and bam, I was greeted with an HTTP Error 403 – Forbidden: Access is denied – You are not authorized to view this page – You do not have permission to view this directory or page using the credentials that you supplied. My heart sank and I tried other domains on the same server but everything was down.

I tried to fix the problem myself but I could not find the root cause of it. I checked whether anonymous access was enabled and whether the IUSR account was working fine. I tried assigning new application pools too but it did not work. I disabled the firewall as well but in vain.

By the time a support ticket was opened and someone fixed the problem, the downtime was just over 2 hours. I hate to think about how many visitors were put off by this and how much money I lost along the way. It was the first time, my sites were offline for such a long time. Anyway the problem was that the stupid lady went into IP Address and Domain Restrictions and turned “Access to unspecified clients” to Deny which caused the 403 problem. By turning this back to allow, the problem was fixed.

feature settings

I did not spot the problem because I did not realise the Feature Settings had more configuration options for the IP Address and Domain Restrictions module. Anyway, I’m glad things are working fine again.

 

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.

Displaying your recent posts in Tumblr

Tumblr does not provide an easy way to show your latest posts on your blog. The infinite scrolling is nice but you want your visitors to be able to see say the last 5 posts you’ve made so they can jump straight to a specific post instead of having to read through all posts until they reach something worth reading. So to achieve this functionality, I’ve used Jquery/JavaScript.

There are 3 things to accomplish this:

  1. If you’re not already using the jquery library, you need to include it in your theme.
  2. Insert an html ul tag where you want the posts to render.
  3. Add the jQuery code to your theme.

Some themes already come with jquery and therefore there’s no need to add it again. However the library needs to appear before the jQuery code for it to work. Check for the word jquery in your theme’s html and if you can’t find any instances of it, then add it in the head section, that is within these tags <head></head>. Then right after it, place the jQuery code. So you will have the following:


<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>

<script type="text/javascript">
$(function() {
var url = 'http://staff.tumblr.com/api/read?start=0&num=5';
var $list = $('#recent-posts');
$.ajax({
 url: url,
 type: 'GET',
 dataType: 'xml',
 success: function(data) {
 var posts = $(data).find('post');
 posts.each( function() {
 var post = $(this);
 var link = post.attr('url-with-slug');
 var title = post.children('regular-title').text();
 if (link && title) {
 $list.append('<li><a href="' + link + '">' + title + '</a></li>');
 }
 });
 }
});
});
</script>

Replace staff.tumblr.com with the hostname of your blog and if you want to change how many posts are displayed, then change the number 5 at the end of the url eg http://staff.tumblr.com/api/read?start=0&num=20. The latter will retrieve the last 20 blog posts.

Now you need to include the following somewhere in your theme where you want the posts to be displayed:


<ul id="recent-posts">
 </ul>

You might want to place that in the sidebar so that it’s prominent. And that’s it really.

Any problems, let me know 🙂