CSS Link Hover Issue in Internet Explorer 7 & 8

I came across a really strange issue yesterday whilst testing a site build in Internet Explorer 6, 7 and 8. For some reason, my anchor link text was not accepting a hover state colour change even though I set the required styles within my style sheet. All other browser accepted the hover styling without any issue.

I decided to create another site in Visual Studio and added the same styling to my links and it worked. After comparing the difference between the original and new style sheet, the only difference was:

/* Original Stylesheet */
body
{
    font-family: Arial;
}
/* New Stylesheet */
body
{
    font-family: Arial, sans-serif;
}

By simply adding “sans-serif” to the font family allowed the hover styles to work correctly in all browsers. I have no idea why making this change resolved my issue.

Google Checkout - Callback Notifications Example

I stated in my last post that when I got better knowledge of using Google Checkout, I will show a full example on how to implement Google’s payment provider.

What I wanted to achieve within my own Google Checkout implementation was the ability to ensure I was retrieving sufficient information from the payment provider. So the transaction data stored within my own database would match exactly what is happening within Google. For example, if a payment was accepted/declined some internal processes need to be carried out. The best way of achieving this was by using a call-back page to process notifications from Google synchronously.

Before we get into the call-back code, we need to login to our Google Checkout account as a merchant and carry out some basic configuration setup. My example’s will be based on a sandbox environment.

Google Account Setup

Integration (Settings > Integration)

The Integration configuration is the most important page since it contains the basis to how we would like Google Checkout to work.

Google Checkout - Integration

As you can see, we have specified a call-back URL that will point to a page in our site. The call-back page is what Google uses to let our site know what’s happening with our transactions and if required, we can create our own internal actions to deal with events that are thrown to us. I have set the notifications to be sent in XML format.

Next, we want to ensure the correct API version is used. Issues are bound to occur is the incorrect version is selected. In this case, I am using the most recent API version: 2.5.

I have unselected Notification Filtering because we want to receive all types of notifications. Google Checkout document states:

Google Checkout responds with a <charge-amount-notification> synchronously after a charge-order request. If you have not selected the option to only receive new order notifications, authorization amount notifications and order state change notifications, Google Checkout will also send you a charge-amount-notification anytime an order has been charged.”

We want to receive a “charge amount notification” to allow us to know if the transaction was charged successfully. This is an actual confirmation to state we have received the money. We shouldn’t assume anything until this is received.

Preferences (Settings > Preferences)

Google Checkout - Preferences

I have setup our order processing to automatically authorise the buyers card for an order. You might be thinking: “This is a bit of manual process…” and you’d be right. If we selected the second option to automatically authorise and charge the buyers card, this would leave a big hole in setting up our internal website processes. By authorising a buyers card gives us more flexibility on how to handle the transaction.

It’s not compulsory for the email option to be selected.

Other Setup

You should be good to go on starting to mess around with implementing your site with Google Checkout. What I haven’t covered is the financial settings such as adding Banking information. This should be a straight-forward process. My main aim is to make sure the basics of Google Merchant account setup is complete.

One really useful page that will help you with investigating problematic transactions is the “Integration Console” (Tools > Integration Console). Generally, this should be empty but any issues will be logged here. This has helped me over the course of integrating Google Checkout in my site.

Call-back Page

Ok. We have Google Merchant Account setup. Now we need to concentrate on our call-back page. My call-back page is based on a customer purchasing a News subscription. We are going to capture all the data Google sends us (excluding refunds) and store them in our own database.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.IO;
using GCheckout;
using GCheckout.AutoGen;
using GCheckout.Util;
using GCheckout.OrderProcessing;
using System.Xml;

public partial class Pay_GCheckout : System.Web.UI.Page
{
    public string SerialNumber = "";

    void Page_Load(Object sender, EventArgs e)
    {
        // Extract the XML from the request.
        Stream RequestStream = Request.InputStream;
        StreamReader RequestStreamReader = new StreamReader(RequestStream);
        string RequestXml = RequestStreamReader.ReadToEnd();
        
        RequestStream.Close();

        // Act on the XML.
        switch (EncodeHelper.GetTopElement(RequestXml))
        {
            case "new-order-notification":
                NewOrderNotification N1 = (NewOrderNotification)EncodeHelper.Deserialize(RequestXml, typeof(NewOrderNotification));
                SerialNumber = N1.serialnumber;
                string OrderNumber1 = N1.googleordernumber;
                string ShipToName = N1.buyershippingaddress.contactname;
                string ShipToAddress1 = N1.buyershippingaddress.address1;
                string ShipToAddress2 = N1.buyershippingaddress.address2;
                string ShipToCity = N1.buyershippingaddress.city;
                string ShipToState = N1.buyershippingaddress.region;
                string ShipToZip = N1.buyershippingaddress.postalcode;

                CustomerSubscription cs = new CustomerSubscription();
                cs.TransactionNumber = OrderNumber1;
                cs.Address1 = ShipToAddress1;
                cs.Address2 = ShipToAddress2;
                cs.Address3 = ShipToCity;
                cs.Region = ShipToState;
                cs.PostCode = ShipToZip;
                cs.TransactionType = CustomerSubscription.TransTypeOnline;
                cs.FinancialOrderState = N1.ordersummary.financialorderstate.ToString();

                foreach (Item ThisItem in N1.shoppingcart.items)
                {
                    string Name = ThisItem.itemname;
                    int Quantity = ThisItem.quantity;
                    decimal Price = ThisItem.unitprice.Value;
                }
                
                if (N1.shoppingcart.merchantprivatedata != null && N1.shoppingcart.merchantprivatedata.Any != null && N1.shoppingcart.merchantprivatedata.Any.Length > 0)
                {
                    foreach (XmlNode node in N1.shoppingcart.merchantprivatedata.Any) 
                    { 
                        if (node.LocalName.Trim() == "customer-id")
                        { 
                            cs.CustomerID = int.Parse(node.InnerText);
                        }

                        if (node.LocalName.Trim() == "subscription-id")
                        {
                            cs.SubscriptionID = int.Parse(node.InnerText);
                        }
                    }
                }

                CustomerSubscription.AddOnlineSubscription(cs);

                break;
            case "risk-information-notification":
                RiskInformationNotification N2 = (RiskInformationNotification)EncodeHelper.Deserialize(RequestXml, typeof(RiskInformationNotification));
                // This notification tells us that Google has authorized the order and it has passed the fraud check.
                // Use the data below to determine if you want to accept the order, then start processing it.
                SerialNumber = N2.serialnumber;
                string OrderNumber2 = N2.googleordernumber;
                string AVS = N2.riskinformation.avsresponse;
                string CVN = N2.riskinformation.cvnresponse;
                bool SellerProtection = N2.riskinformation.eligibleforprotection;
                break;
            case "order-state-change-notification":
                OrderStateChangeNotification N3 = (OrderStateChangeNotification)EncodeHelper.Deserialize(RequestXml, typeof(OrderStateChangeNotification));
                // The order has changed either financial or fulfillment state in Google's system.
                SerialNumber = N3.serialnumber;
                string OrderNumber3 = N3.googleordernumber;
                string NewFinanceState = N3.newfinancialorderstate.ToString();
                string NewFulfillmentState = N3.newfulfillmentorderstate.ToString();
                string PrevFinanceState = N3.previousfinancialorderstate.ToString();
                string PrevFulfillmentState = N3.previousfulfillmentorderstate.ToString();
                break;
            case "charge-amount-notification":
                ChargeAmountNotification N4 = (ChargeAmountNotification)EncodeHelper.Deserialize(RequestXml, typeof(ChargeAmountNotification));
                // Google has successfully charged the customer's credit card.
                SerialNumber = N4.serialnumber;
                string OrderNumber4 = N4.googleordernumber;
                decimal ChargedAmount = N4.latestchargeamount.Value;

                int customerID = 0;

                if (N4.ordersummary.shoppingcart.merchantprivatedata != null && N4.ordersummary.shoppingcart.merchantprivatedata.Any != null && N4.ordersummary.shoppingcart.merchantprivatedata.Any.Length > 0)
                {
                    foreach (XmlNode node in N4.ordersummary.shoppingcart.merchantprivatedata.Any) 
                    { 
                        if (node.LocalName.Trim() == "customer-id")
                        { 
                            customerID = int.Parse(node.InnerText);
                        }
                    }
                }

                if (N4.ordersummary.financialorderstate == FinancialOrderState.CHARGED)
                {
                    CustomerSubscription.UpdateFinancialOrderState(OrderNumber4, N4.ordersummary.financialorderstate.ToString());

                    //Make user paid member
                    CustomerHelper.UpgradeCustomerToPaid(customerID);
                }

                break;
            case "refund-amount-notification":
                RefundAmountNotification N5 = (RefundAmountNotification)EncodeHelper.Deserialize(RequestXml, typeof(RefundAmountNotification));
                // Google has successfully refunded the customer's credit card.
                SerialNumber = N5.serialnumber;
                string OrderNumber5 = N5.googleordernumber;
                decimal RefundedAmount = N5.latestrefundamount.Value;
                break;
            case "chargeback-amount-notification":
                ChargebackAmountNotification N6 = (ChargebackAmountNotification)EncodeHelper.Deserialize(RequestXml, typeof(ChargebackAmountNotification));
                // A customer initiated a chargeback with his credit card company to get her money back.
                SerialNumber = N6.serialnumber;
                string OrderNumber6 = N6.googleordernumber;
                decimal ChargebackAmount = N6.latestchargebackamount.Value;

                break;
            case "authorization-amount-notification":
                AuthorizationAmountNotification N7 = (AuthorizationAmountNotification)EncodeHelper.Deserialize(RequestXml, typeof(AuthorizationAmountNotification));
                SerialNumber = N7.serialnumber;
                string OrderNumber7 = N7.googleordernumber;
                
                // Charge Order If Chargeable
                if (N7.ordersummary.financialorderstate == FinancialOrderState.CHARGEABLE)
                {
                    GCheckout.OrderProcessing.ChargeOrderRequest chargeReq = new GCheckout.OrderProcessing.ChargeOrderRequest(OrderNumber7);

                    GCheckoutResponse oneGCheckoutResponse = chargeReq.Send();

                }

                CustomerSubscription.UpdateFinancialOrderState(OrderNumber7, N7.ordersummary.financialorderstate.ToString());

                break;
            default:                
                break;
        }
    }
}

N.B: The code snippet (above) is shown purely as a simple example for you to build on. It is based on the “notification_handshake.aspx” sample code that is downloadable here.

So what’s going on here? Well the events are processed in the following order:

  1. “new-order-notification” - A new order has been received. Details of the News Subscription along with the customer who ordered it will be stored in our database.
  2. “authorization-amount-notification” - Google tells us that the amount has successfully be authorised. If the order state is “chargeable”, we can go ahead charge the order and update the order state. The customer order state will consist of the following statuses: Cancelled, Chargeable, Charged, Charging, Payment Declined and Reviewing. I think it’s a good to store this information because it will allow a site administrator to view statuses of all orders and act on them, if needed.
  3. “charge-amount-notification” – Google verifies the order was successful. If the order state is marked as charged, the customer will be upgraded to a paid News Subscriber.

Outcome

You have successfully setup Google Checkout payment with basic notifications. If you already have Google Checkout code in place where a user can add one or more items to the shopping cart this code can stay. All you need to do is carry out the changes described in this post.

Integrating Into Google Plus - Is it worth it?

Google Plus When I first heard Google were introducing their own social-networking platform, I was intrigued to say the least on what they could offer compared to the other social sites I use: Facebook and Twitter.

As I stated in one of my earlier posts, I am more of a tweeter since I can share my blog posts easily along with my random ramblings. I think Facebook will have a problem competing alongside Twitter or Google+. Facebook is seen to be more of a personal social network rather than a open professional network and that’s its biggest downfall. It’s quite difficult to cross the boundaries between posting professional/business content alongside personal posts. Thankfully, this is something Google Plus does quite well through its new “circle’s” feature allowing complete control on who see’s what.

I jumped at the chance of using Google Plus when I was offered an invite during the initial release. I was very impressed. Simple and straight-forward. My posts looked really beautiful within its minimalist user interface. Well what else would you expect from Google? Don’t get me started on the eye-sore that is Facebook’s new interface – I’ll leave that for another blog post.

For me, Google Plus is like an extension of Twitter with some added benefits such as:

  • Ability to make posts private/public.
  • Follow people by adding them to a circle.
  • No character limit on the length of posts.
  • Nice interoperability with the search-daddy that is Google.

For a new social networking site, I get a higher click-through-rate to my blog than I ever got compared to tweeting on Twitter. In the process, I managed to get more people adding me to their circle. So take any remarks regarding the inactivity of Google+ with a pinch of salt. I don’t buy it. Google encompasses a big community that you feel part of.

I briefly touched upon the interoperability factor with Google search. People underestimate the power of having the backing of Google search. For example, what if you wrote an article and linked it to your Google+ profile? This information will be displayed as author information within search results to help users discover great content and learn more about the person who wrote the article.

One thing that did surprise me is the fact that at this point in time there’s no advertisement. Unlike its predecessors (yes I that’s how confident I am in Google Plus), you always manage to find advertisement in some form or another. I can view my profile page without constantly having an advert rubbing my single relationship status to my face – something Facebook does far too often.

I trust Google more with my data over Facebook any day. I know Google can’t exactly be trusted either but unlike Facebook they’re not always in the the news on a monthly basis regarding some type of data scandal. At time of writing, it is being reported Facebook is now facing a privacy suit over internet tracking.

In conclusion, integrating ones self into Google Plus is definitely worth it. I only recently started to make more of an effort on Google+ and I find myself posting my content here over other social-networking sites. The key to making a good start is to make some of your posts public to show others your interests and even connect to these type of people either by adding them to a circle or joining a hangout.

On a final note, if you have a Google Plus account and like what I post then why not circle me. :-)

My Alienware m11x Review

Alienware M11xOn hearing Dell have officially acknowledged there is an issue with all Alienware M11x screen hinges (duh!) regardless of warranty status, I decided to write a belated review on my experience of owning this very laptop.

After having a Dell XPS M1210 since 2007, I thought it was about time I looked for a more substantial piece of kit. I believe I still would have been completely happy with my M1210 to this very day if it wasn’t for the dependence of 64 bit architecture on some key software applications I use.

The thing that really impressed me about the m11x was the portability factor. It really is quite a marvel on how the guys at Dell managed to pack so much high spec goodness in such as small light-weight package. I decided to go all out on my laptop specification. As my Dad has always told me: “Always get the best for the time”, especially since technology progresses so fast nowadays and you want your piece of kit to last as long as it can. Very wise my old man!

So my specification is as follows:

  • Processor: Intel i7 1.5Ghz (overclockable to 2.6Ghz)
  • RAM: 8GB
  • Hard Disk Drive: 320GB
  • 2GB nVidia Geforce GT 540M Graphics Card
  • Flashy lights!!! (as standard)

All this for around the £1000 mark. Not bad considering I paid in excess of over £1200 for my old Dell XPS laptop and I was getting quite a lot for my money compared to other gaming machines on the market. A laptop with this setup doesn’t fail to impress when it comes to Windows Experience ratings:

Alienware M11x Experience Index

The experience rating is based on a non-overclocked processor running at 1.5Ghz. I will be interested to see how the rating fairs when the processor has been overclocked…soon as I figured out how to do this.

The Good, The Bad and The Ugly

The Good

Aside from the form factor, the Alienware M11x manages to pack a real punch when performing any task. From opening Notepad (lol!) to playing the most performance and graphic hungry games. I will admit, I am not much of a gamer but my experiences with playing Dirt 3 has been absolutely phenomenal especially with compared to its console counterparts. The icing on the cake has to be playing games externally through a High Definition TV via HDMI.

The cool customisations and light effects you can make to the keyboard, front and logo lights will satisfy the inner geek. A really nice touch to keep your hardware looking different.

The build quality is similar to what I’d expect from Dell. The good and bad. The matte black soft-touch finish allows the laptop to be somewhat scratch resistant and not much of a finger-print magnet compared to earlier editions of the M11x. All panels and covers are rock solid and all sockets and ports are nice and grippy!

Battery life is greatly increased thanks to the nVidia Optimus switchable graphics, which automatically detects if the application you are running needs full graphics power. I manage to get around 5 hours battery life through general use.

The Bad

Only a minor gripe based on my personal preferences is there is no hard drive status light. My previous Dell laptop had this status light to show me how much my disk drive is working. Useful if your computer is locking up.

It would be nice to have an additional powered USB socket to charge more than one appliance when my Alienware is switched off.

The Synaptics touchpad is as responsive as all the other touchpad’s I’ve used in the past. So no issue there. I am just not too keen on the honeycomb textured surface. The finger doesn’t glide as fluidly as I would have liked.

The Ugly

The well known issue I stated at the beginning of my post – faulty screen hinge. I had known that there have been numerous issues with the hinges even before I made my purchase. You would have thought by the third iteration of the M11x this issue would have been solved by now. Unfortunately, it hasn’t.

The problem I have been experiencing from day one is the keyboard touching the screen when the lid is closed. This has resulted in marks on various points on my screen. Unfortunately, by the time I noticed the damage was done. It seems that the keys on the keyboard are not aligned correctly with the base of the unit.

I will be sure to get both screen and hinges replaced.

Conclusion

Overall, I'm really pleased with the laptop. My only gripe is the hinge issue ruining my screen.

Add MemoryStream File To Kentico Media Library

I needed to be able to pass a file that was stored in a MemoryStream into my Kentico Media Library. In my case, the file was a dynamically generated PDF.

I couldn’t find anything on the web on how I would achieve this. So I decided to have a go creating my own method based on the Media Library API and some very basic examples, as you can see below:

public static string AddFile(MemoryStream file, string fileName, string description, string mediaLibrayName, string folder, string fileExt)
{
    string cleanFileName = MakeValidFileName(fileName).Replace(" ", "-");

    string folderDirectory = HttpContext.Current.Server.MapPath(String.Format("/MyWebsite/media/{0}/{1}/", mediaLibrayName, folder));

    string mediaFilePath = String.Format("{0}{1}.{2}", folderDirectory, cleanFileName, fileExt);

    if (!File.Exists(mediaFilePath))
    {
        #region Create File in Media Library Directory

        //Check if directory exists
        if (!Directory.Exists(folderDirectory))
            Directory.CreateDirectory(folderDirectory);

        file.Position = 0;

        FileStream outStream = new FileStream(mediaFilePath, FileMode.Create, FileAccess.Write);
        file.WriteTo(outStream);
        outStream.Flush();
        outStream.Close();

        #endregion

        #region Add file info to Kentico Media library

        //Media Library Info - takes Media Library Name and Website Name
        MediaLibraryInfo libraryInfo = MediaLibraryInfoProvider.GetMediaLibraryInfo(mediaLibrayName, CMSContext.CurrentSiteName);

        // Get Relative Path to File
        string path = CMS.MediaLibrary.MediaLibraryHelper.EnsurePath(mediaFilePath);

        //Create media file info item
        MediaFileInfo fileInfo = new MediaFileInfo(path, libraryInfo.LibraryID, folder);

        fileInfo.FileTitle = fileName;
        fileInfo.FileDescription = description;

        // Save media file info
        MediaFileInfoProvider.ImportMediaFileInfo(fileInfo);

        #endregion

        return String.Format("/MyWebsite/media/{0}/{1}/{2}.{3}?&ext=.{3}", mediaLibrayName, folder, cleanFileName, fileExt);
    }
    else
    {
        return String.Empty;
    }
}

The method I created is generic enough for you use in your own Kentico site. It provides all the necessary parameters needed  to add an image to a media library of your choice. For example:

AddFile(fileMemStream, “Marketing Issue", “Monthly Marketing Info”, "Private", "Premium Folder", "pdf");

As much as a like using the Kentico CMS platform, I find their API documentation some what lacking in examples on how to use certain methods, especially for a new Kentico developer like myself. I am hoping this is something that will change in the near future.

Google Checkout Error - "Expected Serial Number was not contained in notification acknowledgment”

I’ve been busy lately integrating a payment provider into a site I am working in. After looking at the best payment providers, it came down to either using PayPal or Google Checkout. In the end I decided to use Google’s payment provider (as you can probably tell from my post title).

Before I start this post, I assume you have already created a sandbox Google Checkout account and know your way around setup and integration.

For my own Google Checkout integration, I needed to be able to add new transaction item in my database only if a customer has ordered an item and if their payment is approved. This is where Google’s call-back notification service comes into play.

I decided to use an synchronous notification process based on one the sample code that can be downloaded here: http://code.google.com/p/google-checkout-dotnet-sample-code/downloads/list.

Just using the sample code provided by Google out-the-box caused issues. It seems that the code samples do not correctly reflect version changes to the payment provider. One of the repeating errors I got was: “Expected serial number was not contained in notification acknowledgement”.

Google Checkout ErrorList

You will only receive this error only if you are using API version 2.5 and have “Notification Filtering” enabled in account settings. Unfortunately, notification filtering is something I needed to ensure both Google Checkout and my database transactions were in sync.

The Integration Issue error log showed me that the transaction serial number was not getting populated from my call-back notification page.

Google Checkout Error XML Detail

After a lot of debugging it became apparent that the notification page provided by Google Code sample was wrong! It was missing a key case statement: “authorization-amount-notification”. Once this was added no more errors occurred.

If you are using the “notification_extended.aspx”, “notification.aspx” or “notification_handshake.aspx” code samples. Be sure to make the following change:

<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="GCheckout" %>
<%@ Import Namespace="GCheckout.AutoGen" %>
<%@ Import Namespace="GCheckout.Util" %>
<script runat="server" language="c#">

  public string SerialNumber = "";

  void Page_Load(Object sender, EventArgs e) {
    // Extract the XML from the request.
    Stream RequestStream = Request.InputStream;
    StreamReader RequestStreamReader = new StreamReader(RequestStream);
    string RequestXml = RequestStreamReader.ReadToEnd();
    RequestStream.Close();
    // Act on the XML.
    switch (EncodeHelper.GetTopElement(RequestXml)) {
      case "new-order-notification":
        NewOrderNotification N1 = (NewOrderNotification) EncodeHelper.Deserialize(RequestXml, typeof(NewOrderNotification));
        SerialNumber = N1.serialnumber;
        string OrderNumber1 = N1.googleordernumber;
        string ShipToName = N1.buyershippingaddress.contactname;
        string ShipToAddress1 = N1.buyershippingaddress.address1;
        string ShipToAddress2 = N1.buyershippingaddress.address2;
        string ShipToCity = N1.buyershippingaddress.city;
        string ShipToState = N1.buyershippingaddress.region;
        string ShipToZip = N1.buyershippingaddress.postalcode;
        foreach (Item ThisItem in N1.shoppingcart.items) {
          string Name = ThisItem.itemname;
          int Quantity = ThisItem.quantity;
          decimal Price = ThisItem.unitprice.Value;
        }
        break;
      case "risk-information-notification":
        RiskInformationNotification N2 = (RiskInformationNotification) EncodeHelper.Deserialize(RequestXml, typeof(RiskInformationNotification));
        // This notification tells us that Google has authorized the order and it has passed the fraud check.
        // Use the data below to determine if you want to accept the order, then start processing it.
        SerialNumber = N2.serialnumber;
        string OrderNumber2 = N2.googleordernumber;
        string AVS = N2.riskinformation.avsresponse;
        string CVN = N2.riskinformation.cvnresponse;
        bool SellerProtection = N2.riskinformation.eligibleforprotection;
        break;
      case "order-state-change-notification":
        OrderStateChangeNotification N3 = (OrderStateChangeNotification) EncodeHelper.Deserialize(RequestXml, typeof(OrderStateChangeNotification));
        // The order has changed either financial or fulfillment state in Google's system.
        SerialNumber = N3.serialnumber;
        string OrderNumber3 = N3.googleordernumber;
        string NewFinanceState = N3.newfinancialorderstate.ToString();
        string NewFulfillmentState = N3.newfulfillmentorderstate.ToString();
        string PrevFinanceState = N3.previousfinancialorderstate.ToString();
        string PrevFulfillmentState = N3.previousfulfillmentorderstate.ToString();
        break;
      case "charge-amount-notification":
        ChargeAmountNotification N4 = (ChargeAmountNotification) EncodeHelper.Deserialize(RequestXml, typeof(ChargeAmountNotification));
        // Google has successfully charged the customer's credit card.
        SerialNumber = N4.serialnumber;
        string OrderNumber4 = N4.googleordernumber;
        decimal ChargedAmount = N4.latestchargeamount.Value;
        break;
      case "refund-amount-notification":
        RefundAmountNotification N5 = (RefundAmountNotification) EncodeHelper.Deserialize(RequestXml, typeof(RefundAmountNotification));
        // Google has successfully refunded the customer's credit card.
        SerialNumber = N5.serialnumber;
        string OrderNumber5 = N5.googleordernumber;
        decimal RefundedAmount = N5.latestrefundamount.Value;
        break;
      case "chargeback-amount-notification":
        ChargebackAmountNotification N6 = (ChargebackAmountNotification) EncodeHelper.Deserialize(RequestXml, typeof(ChargebackAmountNotification));
        // A customer initiated a chargeback with his credit card company to get her money back.
        SerialNumber = N6.serialnumber;
        string OrderNumber6 = N6.googleordernumber;
        decimal ChargebackAmount = N6.latestchargebackamount.Value;
        break;
    // Edit: Additional case statement for "authorization-amount-notification"
      case "authorization-amount-notification":
        AuthorizationAmountNotification N7 = (AuthorizationAmountNotification)EncodeHelper.Deserialize(RequestXml, typeof(AuthorizationAmountNotification));
        // Information about a successful reauthorization for an order
        SerialNumber = N7.serialnumber;
        string OrderNumber7 = N7.googleordernumber;
        break;
      default:
        break;
    }
  }
</script>
<notification-acknowledgment xmlns="http://checkout.google.com/schema/2" serial-number="<%=SerialNumber %>" />

Once I have got to grips on using Google Checkout I will add another blog post containing my full call-back solution.

Calculate Time Duration in Seconds, Minutes, Hours & Weeks

For a news site I am currently working on, I needed to display the last time a news article was last published. I wanted to be able to show the duration based on respective major time format. For example, if an article was displayed a couple hours ago, I would want it to to display “2 hours” not “120 minutes”.

More importantly, if an article hadn’t been published to the site more than a week, I don’t want the exact time duration to be displayed. I would prefer the following message: “more than a week ago”. This way, if the site administrator gets really lazy the website viewer will not know the exact time period the site was last updated.

Code:

public class TimePassed
{
    public static string GetPassedTime(DateTime since)
    {
        TimeSpan ts = DateTime.Now.Subtract(since);

        if (ts.Days <= 7)
        {
            switch (ts.Days)
            {
                case 0:
                    switch (ts.Hours)
                    {
                        case 0:
                            switch (ts.Minutes)
                            {
                                case 0:
                                    return String.Format("{0} seconds ago", ts.Seconds);
                                case 1:
                                    return "1 minute ago";
                                default:
                                    return String.Format("{0} minutes ago", ts.Minutes);
                            }
                        case 1:
                            return "1 hour ago";
                        default:
                            return String.Format("{0} hours ago", ts.Hours);
                    }
                case 1:
                    return "yesterday";
                default:
                    return String.Format("{0} days ago", ts.Days);
            }
        }
        else
        {
            return "more than a week ago";
        }
    }
}

Get CheckBoxList Values Using jQuery

To be able to retrieve values from a ASP.NET CheckBoxList control or a group of HTML checkboxes, use the following jQuery:

$(document).ready(function () {
    var checkboxValues = [];

    $('#<%=MyCheckBoxList.ClientID %> input[type=checkbox]').click(function () {
        $('input[type=checkbox]:checked').each(function () {
            checkboxValues.push(this.value);
        });        
    });
    
    var values = checkboxValues.toString(); //Output Format: 1,2,3
});

If you do use this code snippet on a CheckBoxList, take a look that this article on how to create a custom CheckBoxList control with a value attribute.

ASP.NET CheckBoxList Control With Value Attribute

ASP.NET server controls is a great way to quickly build a page with dynamic functionality. Even though we do not have much of direct control over the way these controls are rendered, they do a pretty good job and its not very often I get annoyed with them.

Until now.

Generally, I find myself using the .Attributes.Add() method when needing to add additional attributes to certain server controls. No problem! In this case, I wanted to add a “value” attribute that will contain the record ID for that checkbox. I can then use this value within my JavaScript. I would have thought a value attribute would already be there. Its perfectly valid HTML mark-up:

<form>
    <input type="checkbox" name="vehicle" value="Volvo" />
    <input type="checkbox" name="vehicle" value="Volkswagen" />
</form> 

For some reason, when I tried to add my custom attributes after my CheckBoxList was databound (as shown below), the attribute was simply ignored.

NewsCheckList.Items[0].Attributes["value"] = "1";
NewsCheckList.Items[1].Attributes["value"] = "2";
NewsCheckList.Items[2].Attributes["value"] = "3";

So I decided the best way forward would be to create a custom CheckBoxList control that would contain a value attribute. I based my code from an old (but very useful) article that can be found here.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI.WebControls;
using System.IO;
using System.Web.UI;
using System.Collections;
using System.ComponentModel;

namespace Site.WebControls
{
    [DefaultProperty("Text"),
    ToolboxData("<{0}:CheckBoxValueList runat=server></{0}:CheckBoxValueList>")]
    public class CheckBoxValueList : CheckBoxList
    {
        protected override void Render(HtmlTextWriter writer)
        {
            StringBuilder sb = new StringBuilder();
            TextWriter tw = new StringWriter(sb);
            
            HtmlTextWriter originalStream = new HtmlTextWriter(tw);
            base.Render(originalStream);
            string renderedText = sb.ToString();

            int start = 0;
            int labelStart = 0;
            int end = renderedText.Length;

            for (int i = 0; i < this.Items.Count; i++)
            {
                StringBuilder itemAttributeBuilder = new StringBuilder();

                end = renderedText.Length;
                start = renderedText.IndexOf("<input", start, end - start);
                labelStart = renderedText.IndexOf("<label", start, end - start);

                this.Items[i].Attributes.Render(new HtmlTextWriter(new StringWriter(itemAttributeBuilder)));

                renderedText = renderedText.Insert(labelStart + 7, itemAttributeBuilder.ToString() + " ");
                renderedText = renderedText.Insert(start + 7, String.Format("{0} value=\"{1}\" ", itemAttributeBuilder.ToString(), this.Items[i].Value));
                start = renderedText.IndexOf("/>", start, renderedText.Length - start);
            }
            
            writer.Write(renderedText);
        }
    }
}

Has Facebook Redefined Friendship?

Definition Of FriendI was a late bloomer when when it came to joining the social networking giant that is Facebook (around late 2007). The only reason I can remember for ever joining the site was just because all people around me were submitting their profiles like crazy. Not wanting to miss out on this new trend, I decided to “pop” my social networking cherry and take the plunge!

Looking back on my first experience on Facebook I was amazed at how easily I could connect with friends and people I used to know from a past life (school, work etc). Within a few months my Facebook profile spread through the social networking vine in quick haste and found myself receiving friend requests. But it became ever so prevalent that the people who requested me to add them as a friend weren’t people I would necessarily call a friend. I knew of them and that is where my connection ends. So in some ways Facebook has redefined the term “friend”.

Facebook has broken down the friendship barriers considerably. Its made it really easy. Too easy in fact. It was only a couple days ago when my sister said: “Look! I got more friends than you!”. In all honesty I wasn’t really bothered…ok maybe a little. This is where personal feelings come into play.

One of the feelings I will call: “Facebook guilt”. Facebook guilt is when you receive a friend request and don’t act on it. You simply ignore it hoping they would just forget or even worse…remove their request altogether. Hoping by not accepting their friend request you haven’t made an enemy or caused emotional discourse.

Then there is “Facebook rejection”. An example of this is the following conversation I had with with a work mate of mine a few years back:

Anonymous friend: I’ve sent you a Facebook friend request. Me: Ok cool. Anonymous friend: How come you haven’t approved it yet? Me: Mate, I’ve been on holiday for over a week and haven’t checked Facebook yet. I’ll do it today. Anonymous friend: Thanks Surinder!

I was surprised that he took not responding to his friend request as a personal hit.

Social Networking sites have created a trend that makes us more interested in the number of people in our social circles rather than the relationships we have with them.

So where do I stand in the social networking medium? My Facebook activity has drastically declined over the years. Currently, I have 114 friends with majority of them I know quite well and only a handful of them I haven’t really met. Nowadays, I have become more of a tweeter. I just feel that Twitter has met my social needs over Facebook. Its just more flexible and open. If someone likes you they follow you, if not they don’t.