Philip Lippard

Please say it ain't so

Using Paypal Buy Now Buttons with ASP.NET

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.