Using Paypal Buy Now Buttons with ASP.NET

by plippard20. July 2010 11:30

I usually use Paypal Web Services for ECommerce, however for the occasional ECommerce merchant, Paypal Web Services may not be appropriate because of the minimum monthly fee.  Depending on one’s preferences, Paypal Buy Now buttons may be suitable for ECommerce.  Buy Now buttons incur no minimum monthly fee and involve conducting a copy/paste operation of HTML into one’s HTML page, as illustrated below:

   1:<form action="https://www.paypal.com/cgi-bin/webscr" 
   2:method="post">
   3:<input type="hidden" 
   4: name="cmd" 
   5: value="_s-xclick" />
   6:<input type="hidden" 
   7: name="hosted_button_id" 
   8: value="F4E333HY55FXX" />
   9:<input type="image" 
  10: src="https://www.paypal.com/en_US/i/btn/btn_buynow_SM.gif" 
  11: border="0" 
  12: name="submit" alt="PayPal - The safer, easier way to pay online!" />
  13:<img alt="" 
  14: border="0" 
  15: src="https://www.paypal.com/en_US/i/scr/pixel.gif" 
  16: width="1" 
  17: height="1" />
  18:</form>

With ASP.NET the above Paypal Buy Now HTML snippet is problematic because ASP.NET WebForms is limited to a single “form” tag definition.  As an alternative one can use the revised HTML snippet to accomplish the same task.

   1:<input type="hidden" 
   2: name="cmd" 
   3: value="_s-xclick" />
   4:<input type="hidden" 
   5: name="hosted_button_id" 
   6: value="F4E333HY55FXX" />
   7:<asp:ImageButton runat="server" 
   8: ImageUrl="https://www.paypal.com/en_US/i/btn/btn_buynow_SM.gif" 
   9: PostBackUrl="https://www.paypal.com/cgi-bin/webscr" />

You will note that the above HTML snippet has been revised such that the forms tag is removed and we are conducting a postback using the ASP.NET ImageButton control.  This works fine provided we have only a single Paypal Buy Now button per WebForm page.

Let’s consider the situation where we have several product definitions defined within SQL Server along with a Paypal Buy Now HTML button definition within SQL for each product.   Let’s also say we are attempting to use an ASP.NET ListView for conducting runtime data binding, illustrated as follows:

   1:<asp:ListView GroupItemCount="3"
   2:DataSourceID="dataSource"
   3:DataKeyNames="GuidKey"
   4:runat="server"> 
   5:<LayoutTemplate>
   6: 
   7:<table id="productTable" runat="server" 
   8: cellpadding="0"
   9: cellspacing="0"
  10: border="0"> 
  11: 
  12: <tr runat="server" id="groupPlaceholder" />
  13: 
  14:</table>
  15:</LayoutTemplate>
  16:<GroupTemplate>
  17:<tr>
  18: <td runat="server" id="itemPlaceholder" />
  19:</tr>
  20:</GroupTemplate>
  21:<ItemTemplate> 
  22:<td>
  23: <div>
  24: <div>
  25: <asp:Literal runat="server" 
  26: Text='<%# Eval("Title") %>' />
  27: </div>
  28: <div>
  29: <asp:Literal runat="server" 
  30: Text='<%# Eval("Description") %>' />
  31: </div>
  32: <div>
  33: <asp:Literal runat="server" 
  34: Text='<%# InjectImage(Eval("Url")) %>'/>
  35: </div>
  36: <div>
  37: <asp:Literal runat="server" Text='<%# Eval("ButtonHTML") %>' />
  38: <asp:ImageButton runat="server" 
  39: ImageUrl="~/Themes/Images/ECommerce-Buttons/button-add-cart-blue.gif" 
  40: PostBackUrl="https://www.paypal.com/cgi-bin/webscr" />
  41: </div>
  42: </div> 
  43:</td>
  44:</ItemTemplate>
  45:</asp:ListView>

The above illustration assumes that the ButtonHTML SQL field being bound contains the Paypal Buy Now HTML snippet; the snippet that has been revised for ASP.NET.  The above implementation is problematic because Paypal will encounter errors when reading the Paypal hidden fields, because there will be multiple Paypal hidden fields with the same “name” attribute; “cmd” and “hosted_button_id”, etc.

The most effective means that I have found to get around this problem is to continue to define the revised Paypal Buy Now HTML snippet as we doing above, because doing so will simplify the copy/paste operation from Paypal to our SQL Server app.  Ultimately, what we want to do is to transform all hidden field “name” attributes to “id” attributes during WebForm start-up, immediately after the ListView is rendered.  Using jQuery, we transform the productTable ListView as follows:

   1: 
   2: 
   3:function OnAjaxLoad() {
   4: 
   5:
   6:    jQuery(document).ready(function () {
   7:        InitializePaylBuyNowButtons();
   8:    });
   9:}
  10: 
  11:function InitializePaylBuyNowButtons() {
  12: 
  13:    jQuery("table[id$='_productTable']").children('tbody').children('tr').each(function () {
  14:        jQuery(this).children('td').each(function () {
  15:            jQuery(this).children('div').children('#paypalButton').each(function () {
  16: 
  17: var productContainer = jQuery(this);
  18: 
  19: // initialize standard Paypal Buy Now hidden fields - 
  20: // move name attribute value to id attribute
  21: // so these hidden fields will not be visible to Paypal
  22:                productContainer.children('input:hidden').each(function () {
  23: 
  24: switch (jQuery(this).attr("name")) {
  25: case "cmd":
  26: case "hosted_button_id":
  27: case "currency_code":
  28:                            jQuery(this).attr("id", jQuery(this).attr("name"));
  29:                            jQuery(this).attr("name", "");
  30: break;
  31: default:
  32: break;
  33:                    }
  34:                });
  35: 
  36: // find possible drop-down - used for multiple prices - 
  37: // do same name ==> id move
  38:                productContainer.children('table').children('tbody').children('tr')
  39:                    .children('td').children('input:hidden').each(function () {
  40: 
  41: switch (jQuery(this).attr("name")) {
  42: case "on0":
  43:                            jQuery(this).attr("id", jQuery(this).attr("name"));
  44:                            jQuery(this).attr("name", "");
  45: break;
  46: default:
  47: break;
  48:                    }
  49:                });
  50: 
  51: // find possible drop-down - used for multiple prices - 
  52: // do same name ==> id move
  53:                productContainer.children('table').children('tbody').children('tr')
  54:                    .children('td').children('select').each(function () {
  55: 
  56: switch (jQuery(this).attr("name")) {
  57: case "os0":
  58:                            jQuery(this).attr("id", jQuery(this).attr("name"));
  59:                            jQuery(this).attr("name", "");
  60: break;
  61: default:
  62: break;
  63:                    }
  64:                });
  65: 
  66:            });
  67:        });
  68: 
  69:    });
  70:}
  71: 
  72:Sys.Application.add_load(OnAjaxLoad);

After we have transformed all Paypal Buy Now button hidden fields, we want to revise our ListView definition to accommodate the above jQuery start-up transformation as follows:

   1:<asp:ListView GroupItemCount="3"
   2:DataSourceID="dataSource"
   3:DataKeyNames="GuidKey"
   4:runat="server"> 
   5:<LayoutTemplate>
   6: 
   7:<table id="productTable" runat="server" 
   8: cellpadding="0"
   9: cellspacing="0"
  10: border="0"> 
  11: 
  12: <tr runat="server" id="groupPlaceholder" />
  13: 
  14:</table>
  15:</LayoutTemplate>
  16:<GroupTemplate>
  17:<tr>
  18: <td runat="server" id="itemPlaceholder" />
  19:</tr>
  20:</GroupTemplate>
  21:<ItemTemplate> 
  22:<td>
  23: <div>
  24: <div>
  25: <asp:Literal runat="server" 
  26: Text='<%# Eval("Title") %>' />
  27: </div>
  28: <div>
  29: <asp:Literal runat="server" 
  30: Text='<%# Eval("Description") %>' />
  31: </div>
  32: <div>
  33: <asp:Literal runat="server" 
  34: Text='<%# InjectImage(Eval("Url")) %>'/>
  35: </div>
  36: <div id="paypalButton">
  37: <asp:Literal runat="server" Text='<%# Eval("ButtonHTML") %>' />
  38: <asp:ImageButton runat="server" 
  39: ImageUrl="~/Themes/Images/ECommerce-Buttons/button-add-cart-blue.gif" 
  40: PostBackUrl="https://www.paypal.com/cgi-bin/webscr" 
  41: OnClientClick="PaypalBuyNowProductSelected(this);" />
  42: </div>
  43: </div> 
  44:</td>
  45:</ItemTemplate>
  46:</asp:ListView>

You will note that we have revised the following:

  • Added an OnClientClick event handler ( PaypalBuyNowProductSelected(this) ) on the ImageButton ASP.NET control, the purpose of which will be to reverse the effect of the hidden fields start-up transformation, but only for the single selected product.  This will ensure that only a single product is communicating with Paypal during the Buy Now postback operation.
  • Add the “id=paypalButton” attribute to the DIV section containing the Paypal Buy Now HTML snippet for each product and the ImageButton control.  This revision makes the jQuery parsing easier.

Now we need to drop in a jQuery ImageButton OnClientClick event handler:

   1:function PaypalBuyNowProductSelected(o) {
   2: 
   3:    var productContainer = jQuery(o).parent('div');
   4: 
   5:    // activate Paypal hidden fields to properly 
   6:    // associate Paypal product being selected
   7:    productContainer.children('input:hidden').each(function () {
   8: 
   9:        switch (jQuery(this).attr("id")) {
  10:            case "cmd":
  11:            case "hosted_button_id":
  12:            case "currency_code":
  13:                jQuery(this).attr("name", jQuery(this).attr("id"));
  14:                break;
  15:            default:
  16:                break;
  17:        }
  18:    });
  19: 
  20:    // find possible drop-down - used for multiple prices - 
  21:    // locate hidden input field
  22:    productContainer.children('table').children('tbody')
  23:        .children('tr').children('td')
  24:        .children('input:hidden').each(function () {
  25: 
  26:        switch (jQuery(this).attr("id")) {
  27:            case "on0":
  28:                jQuery(this).attr("name", jQuery(this).attr("id"));
  29:                break;
  30:            default:
  31:                break;
  32:        }
  33:    });
  34: 
  35:    // find possible drop-down - used for multiple prices - 
  36:    // locate select field
  37:    productContainer.children('table').children('tbody')
  38:        .children('tr').children('td')
  39:        .children('select').each(function () {
  40: 
  41:        switch (jQuery(this).attr("id")) {
  42:            case "os0":
  43:                jQuery(this).attr("name", jQuery(this).attr("id"));
  44:                break;
  45:            default:
  46:                break;
  47:        }
  48:    });
  49: 
  50:    return true;
  51:}

You will note that the OnClientClick event handler is simply transforming Paypal hidden field “id” attributes to “name” attributes for the single product being selected; thus reversing the effect of the original WebForm start-up transformation for the single product only.

That is it.  Now we have working ASP.NET and jQuery code such that Paypal Buy Now buttons will work with ASP.NET and multiple product definitions.  If we use other Paypal hidden fields they can be manipulated in the same manner as the “cmd” and “hosted_button_id” hidden fields.

Tags: , ,

Technology

Photoshop Containers for Dummies

by plippard25. April 2010 03:25

One common practice in web site design is the use of visual containers.   Containers can take the form of rounded corner containers, shadowed containers and many other variations.   There are also many techniques for simplifying the implementation of containers.  There are many JavaScript based solutions for rounding corners or for expressing a shadow, however the JavaScript solutions are never as visually effective as containers based on a layered Photoshop design.  The Photoshop slice tool is used to cut the needed elements (as small as possible) to be used within a layered DIV container approach, along with cascading style sheet (CSS) styling.  The Photoshop slice tool is used to cut the top header row left and right corners and then a 1 pixel wide element to be repeated between the left and right corners to form a complete header row.  The slice tool is then used to cut the same elements for the bottom footer row, and lastly the slice tool is used to cut the variable height content section left and right edges and 1 pixel wide inner content background.

Using the Photoshop slice tool cannot be avoided, however the ASP.NET implementation can be greatly simplified for repeated container implementations.  The focus of this article is to show how a custom server control can be written and used to programmatically generate variable width and height containers such that the DIV layering complexity is completely abstracted away within the server control.

To illustrate, the following sample web page contains one such custom server control:

   1:<body>
   2:<form id="form1" runat="server">
   3:<div>
   4:<socrates:InjectContainer ID="test" runat="server">
   5: <ContentTemplate>
   6: <br />
   7: <br />
   8: <asp:Button ID="btnStart" runat="server" Text="Start" 
   9: OnClick="btnStart_Click" />
  10: <br />
  11: <asp:Label ID="lblStatus" runat="server" />
  12: <br />
  13: </ContentTemplate>
  14:</socrates:InjectContainer>
  15:</div>
  16:</form>
  17:</body>

 

The ContentTemplate contains a combination of HTML and ASP.NET server controls; in this case a very simple button and status line to display Hello World when the button is depressed, as rendered below:

Image-0001

 

The InjectContainer control will enclose the content within the ContentTemplate template with the various DIV layers necessary to achieve an effective cross browser container implementation.  The Container size is NOT limited in any respects, fully controlled by the cascading style sheet.  In the above example the following HTML is generated and injected into the web page.

   1:<div id="test">
   2:<div class="Header">
   3:<div class="HeaderLeft">
   4: <div class="HeaderRight">
   5: <div class="HeaderContent">
   6: </div>
   7: </div>
   8:</div>
   9:</div>
  10:<div class="Body">
  11:<div class="BodyLeft">
  12: <div class="BodyRight">
  13: <div class="BodyContent">
  14: <br />
  15: <br />
  16: <input type="submit" 
  17: name="test$ctl08$btnStart" 
  18: value="Start"
  19: id="test_ctl08_btnStart" />
  20: <br />
  21: <span id="test_ctl08_lblStatus"></span>
  22: <br />
  23: </div>
  24: </div>
  25:</div>
  26:</div>
  27:<div class="Footer">
  28:<div class="FooterLeft">
  29: <div class="FooterRight">
  30: <div class="FooterContent">
  31: </div>
  32: </div>
  33:</div>
  34:</div>
  35:</div>
  36:</div>

As you can see, most of the generated HTML are the layers of DIV sections; necessary to achieve the visually effective container.  Most of the heavy lifting is done by the InjectContainer custom server control, as well as the CSS style sheet, shown below:

   1:<style type="text/css">
   2:
   3:        body
   4:        {
   5:            background-color: Beige;
   6:            margin-top: 100px;
   7:            margin-left: 100px;
   8:        }
   9:
  10:        #test
  11:        {
  12:            width: 310px;
  13:        }
  14:
  15:        #test .Header
  16:        {
  17:        }
  18:
  19:        #test .HeaderLeft 
  20:        {
  21:            padding-left: 2px;
  22:            height: 3px;
  23:            background-image: url(/Images/Containers/White-Block/HeaderLeftEdge.png);
  24:            background-repeat: no-repeat;
  25:            background-position: top left;
  26:        }
  27:
  28:        #test .HeaderContent
  29:        {
  30:            height: 3px;
  31:            background-image: url(/Images/Containers/White-Block/HeaderContent.png);
  32:            background-repeat: repeat-x;
  33:            background-position: top left;
  34:        }
  35:
  36:        #test .HeaderRight
  37:        {
  38:            padding-right: 8px;
  39:            height: 3px;
  40:            background-image: url(/Images/Containers/White-Block/HeaderRightEdge.png);
  41:            background-repeat: no-repeat;
  42:            background-position: top right;
  43:        }
  44:
  45:        #test .Body
  46:        {
  47:        }
  48:
  49:        #test .BodyLeft 
  50:        {
  51:            padding-left: 2px;
  52:            height: 200px;
  53:            background-image: url(/Images/Containers/White-Block/BodyLeftEdge.png);
  54:            background-repeat: repeat-y;
  55:            background-position: top left;
  56:        }
  57:
  58:        #test .BodyContent
  59:        {
  60:            height: 200px;
  61:            background-image: url(/Images/Containers/White-Block/BodyContent.png);
  62:            background-repeat: repeat;
  63:            background-position: top left;
  64:            text-align: center;
  65:        }
  66:
  67:        #test .BodyRight
  68:        {
  69:            padding-right: 8px;
  70:            height: 200px;
  71:            background-image: url(/Images/Containers/White-Block/BodyRightEdge.png);
  72:            background-repeat: repeat-y;
  73:            background-position: top right;
  74:        }
  75:
  76:        #test .Footer
  77:        {
  78:        }
  79:
  80:        #test .FooterLeft 
  81:        {
  82:            padding-left: 2px;
  83:            height: 9px;
  84:            background-image: url(/Images/Containers/White-Block/FooterLeftEdge.png);
  85:            background-repeat: no-repeat;
  86:            background-position: top left;
  87:        }
  88:
  89:        #test .FooterContent
  90:        {
  91:            height: 9px;
  92:            background-image: url(/Images/Containers/White-Block/FooterContent.png);
  93:            background-repeat: repeat-x;
  94:            background-position: top left;
  95:        }
  96:
  97:        #test .FooterRight
  98:        {
  99:            padding-right: 8px;
 100:            height: 9px;
 101:            background-image: url(/Images/Containers/White-Block/FooterRightEdge.png);
 102:            background-repeat: no-repeat;
 103:            background-position: top right;
 104:        }
 105:
 106:</style>

The InjectContainer control can also be used in a nested manner to generate a different container within a container, provided of course the ID= attribute is different for each InjectContainer instance so each container can be uniquely styled within its companion cascading style sheet.

The C# source code for the InjectContainer custom server control is as follows:

   1:using System;
   2:using System.ComponentModel;
   3:using System.Text;
   4:using System.IO;
   5:using System.Web;
   6:using System.Web.UI;
   7:using System.Web.UI.WebControls;
   8:using System.Web.UI.HtmlControls;
   9:using System.Drawing;
  10:using System.Resources;
  11: 
  12:namespace Socrates.CustomControls.Containers
  13:{
  14:public class MyTemplateContainer : Control, INamingContainer
  15:    {
  16:/// <summary>
  17:/// MyTemplateContainer.
  18:/// </summary>
  19:public MyTemplateContainer()
  20:        {
  21:        }
  22:    }
  23: 
  24:    [
  25:        ToolboxData("<{0}:InjectContainer runat=server></{0}:InjectContainer>"),
  26:        ParseChildren(true),
  27:        PersistChildren(true)
  28:    ]
  29:public class InjectContainer : CompositeControl
  30:    {
  31:private ITemplate m_ContentTemplate = null;
  32: 
  33:private Control RenderHeader()
  34:        {
  35: return RenderBlock("Header", null);
  36:        }
  37: 
  38:private Control RenderBody(MyTemplateContainer i)
  39:        {
  40: return RenderBlock("Body", i);
  41:        }
  42: 
  43:private Control RenderFooter()
  44:        {
  45: return RenderBlock("Footer", null);
  46:        }
  47: 
  48:private Control RenderBlock(string BlockName, 
  49:            MyTemplateContainer i)
  50:        {
  51:            HtmlGenericControl divContainer = new HtmlGenericControl("div");
  52:            divContainer.Attributes.Add("class", BlockName);
  53:            HtmlGenericControl divLeft = new HtmlGenericControl("div");
  54:            divLeft.Attributes.Add("class", BlockName + "Left");
  55:            HtmlGenericControl divContent = new HtmlGenericControl("div");
  56:            divContent.Attributes.Add("class", BlockName + "Content");
  57:            HtmlGenericControl divRight = new HtmlGenericControl("div");
  58:            divRight.Attributes.Add("class", BlockName + "Right");
  59: 
  60: if (i != null)
  61:            {
  62:                divContent.Controls.Add(i);
  63:            }
  64: 
  65:            divRight.Controls.Add(divContent);
  66:            divLeft.Controls.Add(divRight);
  67:            divContainer.Controls.Add(divLeft);
  68: 
  69: return divContainer;
  70:        }
  71: 
  72:protected override HtmlTextWriterTag TagKey
  73:        {
  74:            get
  75:            {
  76: return HtmlTextWriterTag.Div;
  77:            }
  78:        }
  79: 
  80:protected override void CreateChildControls()
  81:        {
  82:            Controls.Clear();
  83: 
  84: if (ContainerClass.Length > 0)
  85:            {
  86: if (base.Attributes["class"] == null)
  87:                {
  88: base.Attributes.Add("class", ContainerClass);
  89:                }
  90:            }
  91: 
  92: base.Controls.Add(RenderHeader());
  93: 
  94: if (ContentTemplate != null)
  95:            {
  96:                MyTemplateContainer i = new MyTemplateContainer();
  97:                ContentTemplate.InstantiateIn(i);
  98: base.Controls.Add(RenderBody(i));
  99:            }
 100: else
 101:            {
 102: base.Controls.Add(RenderBody(null));
 103:            }
 104: 
 105: base.Controls.Add(RenderFooter());
 106:        }
 107: 
 108:        [
 109:            PersistenceMode(PersistenceMode.InnerProperty),
 110:            TemplateContainer(typeof(MyTemplateContainer))
 111:        ]
 112:public ITemplate ContentTemplate
 113:        {
 114:            get
 115:            {
 116: return m_ContentTemplate;
 117:            }
 118:            set
 119:            {
 120:                m_ContentTemplate = value;
 121:            }
 122:        }
 123: 
 124:public string ContainerClass
 125:        {
 126:            get
 127:            {
 128: return (this.ViewState["_ContainerClass"] == null) ? "" : (string)this.ViewState["_ContainerClass"];
 129:            }
 130:            set
 131:            {
 132: this.ViewState["_ContainerClass"] = value;
 133:            }
 134:        }
 135:    }
 136:}

If you have any questions, please feel free to send me an email.

Tags: , , ,

Technology

Powered by BlogEngine.NET 2.6.0.5
Theme by Philip Lippard  (Original by Mads Kristensen)

About the author

Philip Lippard is a resident of Sanibel Island, Florida USA.  Philip develops and hosts enterprise web sites for a select group of corporate clients.

Calendar

<<  August 2014  >>
MoTuWeThFrSaSu
28293031123
45678910
11121314151617
18192021222324
25262728293031
1234567

View posts in large calendar

Month List

RecentPosts