现在的位置: 首页 > 综合 > 正文

ASP.NET 2.0打造购物车和支付系统

2013年09月12日 ⁄ 综合 ⁄ 共 16374字 ⁄ 字号 评论关闭

(一)

摘要 在本系列文章中,我们基于ASP.NET 2.0构建了一个简化的在线购物车和PayPal系统;并通过这个示例,对ASP.NET 2.0中新引入的GridView控件作了深入研究。

  到目前为止,我几乎在每一个我参加过的商业C#.net工程中使用了DataGrid控件;因此,当我第一次听到有关于GridView是如何提高工作效率时,我飞快地试用了它。DataGrid和GridView都是ASP.NET 2.0中提供的新的控件类,它们允许你快速容易地显示表格式数据;并且当在线观看它们时,它们都能被转换为客户端HTML表格进行显示。

  一. 简介

  这是系列文章的第一篇。在本篇中,我们将通过一个简单的网上商店示例程序来集中讨论GridView控件的一些用法。注意,在每一篇文章中我们都使用相同的源文件。为了观察本文示例演示效果,你只需要把下载内容解压到你的web服务器上一个新的目录并浏览到该目录名即可。例如,如果你把所有的内容解压到一个你的web服务器根目录下的目录"gridviewshop",并导航到这个目录:
http://www.yourserver.com/gridviewshop

  如果一切顺利,那么你应该会看到一个如下图1所示的站点:


图1.本系列文章网上商店演示程序快照。

  二. GridView

  如果你已经使用DataGrid实现了你的系统,包括你自己的定制分页与排序方案,那么,你真正不需要考虑更新到GridView;因为从终端效果来看,它们都生成相同的内容(都生成一个HTML表格)。然而,如果你刚开始开发一个新的系统,那么,我建议你使用GridView,特别是如果你想利用它内置的分页与排序功能的话。

  通过在设计时刻设置各种属性,你可以控制GridView从外观到功能等若干方面。在本系列文章后面,我们将会更为深入地探讨这些方面,通过把一些CSS类指派给表格行和表格列头;当然,还要添加一些事件处理器以便允许用户与每一行数据进行交互。

  填充GridView类似于填充一个DataGrid。你只需创建DataSource,然后使用如下代码把它绑定到GridView即可:

myGridView.DataSource = yourDataSource;
myGridView.DataBind();

  当然,借助于.NET 2.0,你还有另一种选择,那就是创建一个SqlDataSource并把GridView直接绑定到其上。这是通过设置它的DataSourceID以匹配你指派给SqlDataSource的ID实现的,即是:

<!--使用mySqlDataSource的ID创建SqlDataSource-->
<asp:SqlDataSource
id="mySqlDataSource"
runat="server"
DataSourceMode="DataReader"
ConnectionString="<%$ ConnectionStrings:MyNorthwind%>"
SelectCommand="SELECT LastName FROM Employees">
</asp:SqlDataSource>
<!--创建GridView并且指派它的DataSourceID以匹配上面的mySqlDataSource-->
<asp:GridView
id="myGridView"
runat="server"
autogeneratecolumns="true"
DataSourceID="mySqlDataSource"/>

  作为个人,我并不太看重这种方法,尽管它是微软推荐的建立你的GridView的方法。我比较喜欢更多地控制我的DataSource;因为,这样以来我能够手工过滤它的内容甚至更多,这也正是为什么我在这个商店演示程序中没有使用这个方法的原因。

  好,下面让我们继续讨论构建本文中的商店演示程序。其大致情况是,在一个页面上存在两个GridViews;你在前面已经看到这个图像。一个GridView用于显示我们的商店的产品,而其它的内容对应于购物篮。你能够容易地把这两部分拆分到它们各自的页面中,但是为了简化起见,我们把这些内容放到了一起。

  如果你打开Default.aspx(它包括在本文相应的zip源码文件中),你能够看到这个页面是如何建立的。大多数HTML仅仅用于实现包装之目的;需要注意的是,位于页面顶部的声明以及主<form>标签和位于其内的<GridView>标签。

  三. 页面声明

<%@ page inherits="shop.site" src="cs/site.aspx.cs" %>

  该页面声明简单地告诉我们的页面它属于什么命名空间和类。在这个例子中,我们的命名空间是"shop"而我们的类是"site"。还存在一个称为"src"的额外属性定义,它指向包含该站点类的普通的.cs文本文件。

  我通常在开发期间,把我的类放在外部.cs文件中,并把它们手工地编译成.dll文件。当我使用Visual Studio时,在开发期间,我总是习惯使用预编译的dll,因为稍后,只需要一个简单的构建即可以生成它们。一旦我完成了相应的工作,我都会把该类构建成预编译的dll;但是,在开发期间,我比较喜欢把较多的时间花费在编码方面而不是编译上。

  四. 构建数据

<asp:GridView
id="gvBasket"
AutoGenerateColumns="false"
ShowHeader="True"
ShowFooter="True"
DataKeyNames="id"
OnRowDataBound="gvBasket_RowDataBound"
runat="server">
<Columns>
<asp:ImageField DataImageurlField="thumb" alternatetext="Product Thumbnail" readonly="true" />
<asp:TemplateField HeaderText="Item">
<ItemTemplate>
<h3><asp:Literal id="litItemName" runat="server" /></h3>
</ItemTemplate>
<FooterTemplate>
<a href="delivery-costs.aspx" title="View the list of delivery charges">Delivery Charges</a>
<br /><hr />
<b>Total</b>
</FooterTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>

  如果你仔细地观察一下这两个GridViews,你会注意到它们都把AutoGenerateColumns设置成false。如果没有这一行,或如果它被设置为true,那么,当我们绑定DataSource时,我们的列就会被创建。通过关闭这个特征,我们能够使用"Columns"子标签来定义自己的列。使用这一特征,我们能够创建许多不同类型的列。在这个演示程序中,我们使用了ImageField和TemplateField列类型。该ImageField列把一个图像路径作为它的值(通过DataImageUrlField属性),然后在它自己的列内显示该图像(当生成到该页面时)。

  TemplateField是真正重要的列。它允许你定义一个HeaderTemplate,一个ItemTemplate和一个FooterTemplate。这三个标签允许你把任何内容放到这些地方。其中,HeaderTemplate和FooterTemplate都引用该列的页眉和页脚,而ItemTemplate引用body内容。

  如果你观察一下购物篮GridView,你会看到我们已经使用ItemTemplate来显示购物篮中每一项的名字,价格和数量;然后,我们在FooterTemplate内显示运送费用及总价。上面的片断仅显示"name"列和它的页脚;完整的实现,请参考default.aspx源文件。因为每一列的页眉都是静态文本,所以我们使用HeaderTemplate跳过,并代之使用了TemplateField的HeaderText属性。为了观察一个GridView的页眉和页脚,你必须把GridView的ShowHeader和ShowFooter属性都设置为True。

  使用ItemTemplate的另一个原因在于,你可以把其它HTML和.net标签放于其中。在这个演示程序中,存在若干不同的类型标签,包括<input>,<br/>,<hr/>,<asp:Literal>和<a>。把所有这些标签放到一个ItemTemplate标签内的唯一问题是,你必须多做一些工作来预填充它们,但是并不需要太多工作。第一步是设置GridView的RowDataBound事件。你可以指派一个函数给这个事件(在我们绑定DataSource后,在每次创建一行时,调用这个事件)。你可以在购物篮的GridView属性中看到这一点:
OnRowDataBound="gvBasket_RowDataBound"

  相比之下,产品GridView相应的对应功能更简单些,但是它仅显示如何填充ItemTemplate而不是Header或Footer模板。

  五. 一个重要的函数

  现在,让我们看一下本示例程序中位于"cs"文件夹下的主要的类文件"site.aspx.cs",并且定位到一个称为gvBasket_RowDataBound的函数。下面是该函数的主要实现(当然,你可以参考下载源码检查该文件的其它部分):

protected void gvBasket_RowDataBound(object sender, GridViewRowEventArgs e)
{
 switch( e.Row.RowType )
 {
  case DataControlRowType.DataRow:
   //名称/描述
   ((Literal)e.Row.FindControl("litItemName")).Text = Convert.ToString(((DataRowView)e.Row.DataItem)["name"]);
   //数量
   string quantity = Convert.ToString(((DataRowView)e.Row.DataItem)["quantity"]);
((HtmlInputText)e.Row.FindControl("itProductQuantity")).Value = quantity;
   //价格
   ((Literal)e.Row.FindControl("litPrice")).Text = String.Format("{0:C2}", Convert.ToDouble(((DataRowView)e.Row.DataItem)["price"]) * Convert.ToInt32(quantity));
   break;
  case DataControlRowType.Footer:
   DataTable dtShop = getBasketDt();
   double total = 0.00;
   for(int i = 0; i < dtShop.Rows.Count; i++)
   {
    total += Convert.ToInt32(dtShop.Rows[i]["quantity"]) * Convert.ToDouble(dtShop.Rows[i]["price"]);
   }
   ((Literal)e.Row.FindControl("litTotalQuantity")).Text = Convert.ToString(dtShop.Compute("SUM(quantity)", ""));
   ((Literal)e.Row.FindControl("litDeliveryPrice")).Text = String.Format("{0:C2}", Convert.ToDouble(calcDeliveryCost(total)));
   ((Literal)e.Row.FindControl("litTotalPrice")).Text = String.Format("{0:C2}", Convert.ToDouble(calcDeliveryCost(total)) + total);
   break;
  }
}

  我们要做的第一件事是在RowType属性上执行一个切换,这样我们就能够区别我们在填充一个Header,Footer还是Item模板;因为所有这三个都是单独调用这同一个函数。对于产品和购物篮来说,我们都取得DataControlRowType.DataRow行类型,因为这是我们的ItemTemplate。

  因为我们给HTML页面中的所有控件都确定了唯一的ID,所以我们能够使用行中的FindControl函数。这将返回一个"Object",如果该行中的任何控件有一个相匹配的ID话。我们可以把它强制转换成我们期望的对象类型,例如一个"Literal"或一个"HtmlInputText"域,然后经由它的TextorValue属性把数据指派给它。在每次绑定一个行时,它都被经由GridViewRowEventArgs.Row属性传递给该函数。使用这种技术,我们就能够存取该行的DataItem,它包含来自于DataSource的所有的行数据。然后,由我们来决定我们想从中提取哪些数据以及如何使用它。
在购物篮中,我们从DataItem中提取了名称,数量和价格三列数据,并且把它们指派给我们嵌入式在ItemTemplate中的相关控件。对于DataControlRowType.Footer,情况基本一致,除了我们从会话状态提取DataSource的一个副本之外(getBasketDt();),因为我们想使用所有行中的信息生成总值及运送费用,而不仅仅是传递到该函数中的单行数据。

  六. 结论

  我希望通过本文,你已经掌握了使用GridView控件的基本知识及其它一些技巧。我们分析了实现GridView控件的一种方法以及如何控制其内容的生成。在下一篇中,我们将探讨GridView控件的数据来源,并与你共同建立实际的购物篮。

(二)

在本篇中,我们将经由一个简单的网上商店演示程序来探讨GridView,并开始分析一种生成GridView的DataSource的方法,然后继续使用该数据来创建一个完全功能的购物接口。注意,在这个演示程序中的DataSource是可以自由创建的。

  一、 简介

  在上一篇中,我们讨论了什么是GridView以及如何使用它,包括实际数据如何绑定到其上。在本文中,我们将更密切地分析这些数据的来源以及如何与GridView一起使用它来实现一个简单的购物接口。

  二、 数据来自于何处?

  从根本上讲,这个问题要依赖于你要干什么。它可以来自于一个静态XML文件,一个动态的XML馈送,一个数据库,或许它是自由创建的。但是,无论如何,应该确保满足:如果存在数据,你能够确保它能够"导入"到一个GridView中。在本文中,在每次重启动应用程序时,这部分数据都是自由创建的。

  用于填充两个GridView的DataSource是一个DataTable。它是使用DataColumns和DataRows构建的。在这个主类文件内存在一个称为"createProductDT"的函数,它说明了DataTable的初始创建方式。下面是该函数的完整实现:

private DataTable createProductDT()
{
 DataTable dtProducts = new DataTable();
 DataColumn productColumn = new DataColumn();
 productColumn.DataType = System.Type.GetType("System.Int32");
 productColumn.ColumnName = "id";
 productColumn.Unique = true;
 dtProducts.Columns.Add(productColumn);
 productColumn = new DataColumn();
 productColumn.DataType = System.Type.GetType("System.String");
 productColumn.ColumnName = "thumb";
 dtProducts.Columns.Add(productColumn);
 productColumn = new DataColumn();
 productColumn.DataType = System.Type.GetType("System.String");
 productColumn.ColumnName = "name";
 dtProducts.Columns.Add(productColumn);
 productColumn = new DataColumn();
 productColumn.DataType = System.Type.GetType("System.Double");
 productColumn.ColumnName = "price";
 dtProducts.Columns.Add(productColumn);
 productColumn = new DataColumn();
 productColumn.DataType = System.Type.GetType("System.Int32");
 productColumn.ColumnName = "quantity";
 dtProducts.Columns.Add(productColumn);

 //使"id"成为主键
 DataColumn[] pkColumns = new DataColumn[1];
 pkColumns[0] = dtProducts.Columns["id"];
 dtProducts.PrimaryKey = pkColumns;
 return dtProducts;
}

  首先,我们创建了一个DataTable对象,然后创建一个DataColumn。对于大多数表格列来说,我们仅需要设置数据类型和列名,尽管对于第一列("id")来说,我们还要把它设置为唯一的。这是因为我们要把它作为我们的主键;另外,在函数最后处还要求对之进行配置。之所以我们要使id总是唯一的,是因为我们要使用它来引用我们将在后面添加到DataSource上的各种产品;这样以来,我们能够从中选择特定的数据,例如只使用产品的价格与产品名。这个函数将返回一个空的DataTable,并因此仅被使用于getBasket()和populateProducts()中。

  现在,我们开始把实际的行数据添加到populateProducts()内的DataSource,详见下面的代码。每行对应一个不同的产品。添加一个新行到一个DataTable要求你创建一个新的DataRow,然后调用该DataTable的NewRow()函数。这将在DataTable内为新行留出位置,但是它不会实际地添加该行。

private void populateProducts()
{
 //创建基本结构
 DataTable dtProducts = createProductDT();
 //把产品添加到其上
 //创建初始的行
 DataRow aProduct = dtProducts.NewRow();
 aProduct["id"] = 11;
 aProduct["thumb"] = "images/widget0.jpg";
 aProduct["name"] = "Red Widget";
 aProduct["price"] = 19.99;
 dtProducts.Rows.Add(aProduct);
 //重用该行以添加新产品
 aProduct = dtProducts.NewRow();
 aProduct["id"] = 22;
 aProduct["thumb"] = "images/widget1.jpg";
 aProduct["name"] = "Green Widget";
 aProduct["price"] = 50.99;
 dtProducts.Rows.Add(aProduct);
 //把DataTable绑定到产品GridView
 gvProducts.DataSource = dtProducts;
 gvProducts.DataBind();

 //把产品存储到Session
 Session["dtProducts"] = dtProducts;
}

  首先,我们需要把一些数据添加到该行(例如id,缩略图像的路径,名称和价格)。一旦添加上这些内容,我们即可以调用Add()函数来实际地把我们的新行添加到DataTable。在该演示程序中,我们添加了六种产品,尽管在上面的片断中我们仅添加了两种。你能够看出我是如何实现"欺骗"的并且仅重用了相同的列和相同的行。一旦实现这一点,我们即把DataTable绑定到我们的GridView。详见下面的代码:

gvProducts.DataSource = dtProducts;
gvProducts.DataBind();

  还记得我们在第一篇中所讨论的RowDataBound事件吗?好,一旦我们调用了DataBind(),该函数被激活,即开始在页面上创建我们的数据。你应该清楚,在底层实现上,在这两个事件之间可能还会有其它事件发生;但是,为了更易于理解起见,我们仅考虑这两个事件。

  此后,我们还在会话状态中存储该DataTable的一个副本;这样以来,在以后我们每次想存取产品数据时,我们可以直接检索它而不必重新创建它。值得注意的是,尽管这种情况比较适合于针对一个小规模工程的一少部分数据;但是,当针对大型的应用程序时,你不应该象本例中这样使用会话状态-它会很容易地"吞掉"你的服务器内存;因此,即使使用一部分数据也有可能使用大量的内存,如果存在上千的用户同时访问它的话。在这个演示程序中,你将看到数据被从会话中多次提取;但是,在实际中,你可能实现众多数据库调用以便提取特定的数据子集(当需要它时)。

  值得注意的一个事情是,你能够设置"DataKeyNames",这些内容能够用来索引GridView中的项。产品列表和购物篮都分别实现单个DataKeyName:

DataKeyNames="id"

  这样,当后来点击"Add to basket"按钮以标识我们想添加的产品时使用它。在购物篮中,当更新数量时也使用它。你可以有多个键名,尽管大多数情况下你仅需要一个。

  在填充GridView前,你能够把一个空DataTable绑定到它上面。这将迫使它显示一个空行(你可以使用一个字符串来预填充)。在该演示中,这是使用两个GridView实现的,尽管你仅能看到其中的一个对应于购物篮,因为即使在你的商店中不存在产品也并不重要。你可以象下面这样使用"EmptyDataText"GridView属性来设置它:

EmptyDataText="~Basket is empty~"

  然后,它会象下图1这样被生成:


图1.购物篮为空时的提示。

  三、购物篮


图2.示例程序中实现的购物篮。

  购物篮(参考图2)用于存储产品(顾客通过点击紧邻每一种产品的"Add to basket"按钮从产品列表中选择)。把购物篮存储在会话状态中的实现是不错的技术,因为在一次完整的购物中,在任何时候顾客决定离开你的站点,或可能倒空他们的购物篮时,所有这些数据都有可能被丢弃。当然,由于若干原因,例如为了市场调查目的以标识谁在分析什么以及判断购物潮流等时,你还可以选择把顾客的购物篮内容存储在一个数据库中。另一个理由可能是,向他们展示"Last time you were here you looked at these items ..."类型显示。这要求你有一个方法来区分顾客。两种通常使用的技术是,把一个cookie存储在用户自己的系统中-通过使用一个唯一的ID来标识他们的未来访问,或使用他们的登录ID来加以区别(如果你已经实现顾客登录的话)。

  更新的购物篮还使用createProductDT()函数来创建它的初始的空DataTable。在本演示程序中,我们将使用相同的表格结构,但是你可以通过删除一些数据列来进一步"提炼"你的购物篮。在大多数情况下,你仅需要存储每种产品的ID和数量,由于你能够容易地基于它的ID查找实际的产品细节。

  每次经由产品列表把一个产品添加到篮中时,它的"Add to basket"按钮都会激活一个OnServerClick事件:

protected void shopBuy_OnServerClick(object source, EventArgs e)
{
 int index = ((GridViewRow)((HtmlInputButton)source).Parent.NamingContainer).RowIndex;
 addToBasket(Convert.ToInt32(gvProducts.DataKeys[index].Value));
}
protected void addToBasket(int productID)
{
 DataTable dtBasket = getBasketDt();
 //循环遍历购物篮并检查是否该项已经存在
 bool found = false;
 for(int i = 0; i < dtBasket.Rows.Count; i++)
 {
  if(Convert.ToInt32(dtBasket.Rows[i]["id"]) == productID)
  {
   //增加数量并且标记为已发现
   dtBasket.Rows[i]["quantity"] = Convert.ToInt32(dtBasket.Rows[i]["quantity"]) + 1;
   found = true;
   //当我们已经找到一项时跳出循环
   break;
  }
 }
 //如果该项没有找到,则把它添加为一个新行
 if(!found)
 {
  DataTable dtProducts = getProductsDt();
  DataRow drProduct = dtProducts.Rows.Find (productID);
  //现在,我们已经从数据源中得到了需要的数据,那么我们将把一个新行添加到购物篮中
  DataRow newRow = dtBasket.NewRow();
  newRow["id"] = drProduct["id"];
  newRow["name"] = drProduct["name"];
  newRow["price"] = drProduct["price"];
  newRow["quantity"] = 1;
  dtBasket.Rows.Add(newRow);
 }
 //把新更新的购物篮存储回会话中
 Session["dtBasket"] = dtBasket;
 //更新购物篮,也即是"重新绑定它"
 updateShopBasket();
}

  我们是使用shopBuy_OnServerClick()函数来"捕获"这一点的(这个函数能标识按钮属于哪一行),得到相关产品的ID并用它来调用addToBasket()。在该函数内,我们可以使用给定的产品ID来检查购物篮。如果它已经存在于购物篮中,那么我们需增加它的数量;而如果它不存在,那么我们把它添加为一个新行。最后,我们把购物篮重新绑定到它的更新的DataSource上。参考图3。


图3.实际使用中的购物篮。

  该购物篮,就象产品GridView一样,也使用TemplateColumns;因此,我们可以在每一行上建立一个数量文本框。这为顾客提供一种容易的方式来更新他们要求的每一种商品的数目。一旦他们改变了这些值,他们点击在购物篮下面的"Update Quantities"按钮。这将激活一个为shopUpdateBasketQuantities_OnServerClick()所捕获的OnServerClick事件。这类似于addToBasket()函数:我们必须定位购物篮中的产品,然后更新它的数量。区别在于:当检查从文本框中检索的数据时,我们必须小心,因为你根本不会知道什么人能够进入到其中致使弄乱你的系统。下面是处理这一检查的函数的部分代码片断:

//从Quantity文本框中读取数据
HtmlInputText itQuant = (HtmlInputText)row.FindControl("itProductQuantity");
//把该值转换成一个整数
try
{
 int quant = Convert.ToInt32(itQuant.Value);

 /*如果该值成功转换成一个整数,那么我们还
 需要检查它不是一个负数;否则的话,我们可能欠
 顾客钱!*/
 if(quant > 0)
 {
  drProduct["quantity"] = quant;
 }
 else
 {
  drProduct.Delete();
 }
}
catch
{
 //如果我们不能把它转换成整数,那么我们不作什么改变。
}

  例如,如果有人在quantity域中输入-100,你可能还会欠他们的钱!不过,一般地,你可能不会把钱支付给他们,但是这要依赖于你的支付系统是如何建立的。由于这个原因,我们把这个整数分析包装到一个try/catch语句块内,以便在不能分析的情况下,我们保留原来的值不变。此后,我们检查这个quantity以确保它大于零。如果它小于或等于零,那么我们删除这一行。最后,在检查完购物篮中所有的产品并且修改它们各自相应的数量后,我们即保存购物篮并更新显示。

  购物篮的最后一个关键组成是updateShopBasket()函数:

private void updateShopBasket()
{
 gvBasket.DataSource = getBasketDt();
 gvBasket.DataBind();
 ibEmptyBasket.Visible = ibUpdateBasketQuantities.Visible = ibBasketCheckout.Visible = gvBasket.Rows.Count > 0;
}

  这个函数能够从会话状态中提取购物篮的一个副本,这反过来将创建会话购物篮,如果它已经不存在的话,然后绑定到GridView。其最终目的是隐藏或显示三个购物篮按钮;因为如果购物篮为空的话,不需要显示它们。

  四、一个值得注意的安全问题

  在你的系统的用户有机会输入数据的任何地方都应该严格控制以确保他们没有输入任何不想实现的内容。一个普通问题就是SQL注入。在这种位置,有些人可以把SQL代码输入到一个站点的某个部分,然后你可以在你想使用的原始SQL语句内使用它。所以,比方说相应于quantity域,你可以使用:

"UPDATE tbl_basket SET quantity = " + quantity.Text + " WHERE user_id = " + user_id;

  如果顾客在"quantity"文本框内输入6并且他们的登录id是230,那么上面的代码将看起来象:

UPDATE tbl_basket SET quantity = 6 WHERE user_id = 230;

  而如果顾客输入:

" 1 WHERE 1 = 1; DROP tbl_users; --"

  那么,原始语句现在看起来象:

UPDATE tbl_basket SET quantity = 1 WHERE 1 = 1; DROP tbl_users; -- WHERE user_id =;

  这样以来,他们可以使用"1 WHERE 1 = 1;"来完成原始语句,然后继续"Drop tbl_ users;"操作,这很不妙!最后,他们可以注释掉原始语句的其它部分。其实,这仅是一个极其简单的示例。有关于SQL注入的问题,你可以在网站上搜到许多信息。

  五、 支付

  存在许多种使用电子业务方式接收支付的方法。下面列出几种:

  · 在线商店实际上并不仅仅是一个在线目录,顾客往往还必须能够电话联系到你以便进行订购。

  · 类似上面这种情形,除非你亲自找到顾客来完成整个交易。如果这是有关一些建筑方面的工作(例如一个院子或一个厨房),并且在实地考察之后你需要当场向他们提出一个报价,那么这可能很重要。

  · 使用一种内置安全的支付方法。通过这种方法,顾客能够输入他们的信用卡细节并且可以由系统自动处理交易。

  · 使用例如PayPal、Worldpay或DebiTech等一种外部支付方法。

  本文中的演示商店基于一种旧式风格的使用PayPal接收支付的方法。它应该与其它外部支付系统(例如稍经修改的WorldPay)结合在一起工作。我们之所以说是"旧式风格"是因为,现在的PayPal一般都提供其自己的.net工具包-实现它们自己的连接到它们的站点的系统。

  整个收集购物篮数据并把它转移到PayPal的系统都是在shopBasketCheckout_OnServerClick()函数内实现的:

protected void shopBasketCheckout_OnServerClick(object source,EventArgs e)
{
 string postData = "";
 postData += "currency_code=GBP";
 postData += "&cmd=_cart";
 postData += "&business=youremailaddress@yourdomain.net";
 postData += "&upload=1";
 postData += "&cancel_return=www.davidmillington.net";
 DataTable dtBasket = getBasketDt();
 double total = 0.00;
 for(int i = 0; i < dtBasket.Rows.Count; i++)
 {
  postData += "&item_name_" + (i + 1) + "=" +
  dtBasket.Rows[i]["name"];
  postData += "&quantity_" + (i + 1) + "=" +
  dtBasket.Rows[i]["quantity"];
  postData += "&amount_" + (i + 1) + "=" +
  Convert.ToDouble(dtBasket.Rows[i]["price"]);
  total += (Convert.ToDouble(dtBasket.Rows[i]
["price"]) * Convert.ToInt32(dtBasket.Rows[i]["quantity"]));
  if(i == dtBasket.Rows.Count - 1)
  {
   postData += "&shipping_" + (i + 1) + "=" + calcDeliveryCost(total);
  }
  else
  {
   postData += "&shipping_" + (i + 1) + "=0.00";
  }
  postData += "&shipping2_" + (i + 1) + "=0.00";
  postData += "&handling_" + (i + 1) + "=0.00";
 }
 postData += "&handling=" + calcDeliveryCost(total);
 byte[] data = Encoding.ASCII.GetBytes(postData);
 HttpWebRequest ppRequest = (HttpWebRequest)
WebRequest.Create("https://www.paypal.com/cgi-bin/webscr");;
 ppRequest.Method = "POST";
 ppRequest.ContentType = "application/x-www-form-
 urlencoded";
 ppRequest.ContentLength = data.Length;
 //发送
 Stream ppStream = ppRequest.GetRequestStream();
 ppStream.Write(data, 0, data.Length);
 ppStream.Close();
 //接收
 HttpWebResponse ppResponse = (HttpWebResponse)ppRequest.GetResponse();
 StreamReader sr = new StreamReader(ppResponse.GetResponseStream());
 string strResult = sr.ReadToEnd();
 sr.Close();
 //输出到屏幕
 Response.Clear();
 Response.Write(strResult);
 Response.End();
}

  因为看起来没有一种办法使一个C#应用程序实现寄送并重定向到另一个站点(就象你通常使用一个<form > action属性所实现的那样),所以我们必须采用一种稍微不同的方法。我们构建了一个long型字符串,它包含多个名/值对,然后使用HttpWebRequest和HttpWebResponse对象来从支付服务中来回发送与接收数据。

  该函数的第一部分指定PayPal帐户细节,例如使用的货币,帐户名以及PayPal应该把顾客返回的页面(如果他们决定取消该交易的话)。

  下一步是遍历购物篮并且检索所有我们想传递到PayPal的产品信息。这包括产品名称、数量和价格。由于该演示程序的特点,我们在运送费用方面稍微施加了一点技巧并且把整个运送费用添加到购物篮中最后一件产品上而不是添加到每一件产品。这是因为我们仅基于购物篮的总价求出总的运送费用,而不是基于任何产品种类。

  现在,我们来讨论有趣的部分。我承认,这是我通过Google引擎查询的结果。首先我们创建一个Request对象,当我们经由一个Stream联系到PayPal时使用它。我们使用一个Response对象来接收该响应并简单地把它通过Response.Write()输出到屏幕。这可以把整个购物篮信息输送到PayPal站点并把它导向正确的帐户。

  现在的问题是,顾客到达的第一个页面在相应的地址栏内仍然有你的商店地址。如果他们点击该PayPal站点的任何链接,例如观看购物篮内容或进行登录的话,那么该地址应该相应地改变以反映它确实是PayPal。你可能意识到,有些人可能会被误解,因为这样的事实-他们仍然能够在地址栏中看到你的商店的地址并且甚至可能认为你在试图骗取他们的PayPal或银行帐户细节。如果你正在计划经由一个外部系统例如PayPal或WorldPay来实现支付,那么你应该检查它们的开发者站点来看一下他们推荐的.net方案是什么。

  六、结论

  在本节中,我们首先分析一种生成GridView的DataSource的方法,然后继续使用该数据来创建一个全功能的购物接口。尽管该演示程序中的DataSource可以自由创建;但是,如果你或者有大量的产品或仅拥有一个经常改变的产品线的话,你确实应该需要考虑使用一个数据库来存储你的产品信息。当然,把一个数据库添加到系统中等于打开了它自己的病毒库;因此,这是一种不应该轻易采取的措施。

  需要特别注意的地方是支付系统。该演示商店使用一个很简单的方法来收集要求的购物篮信息并把它发送到一个外部支付系统。你可能想使用更多的控件来实现支付处理,例如提取顾客的支付细节并把它们存储到一个数据库,或编写你自己的电子销售点功能。不管你选择什么方法,你应该清醒地认识到在你的国家实现接收和支付的合法性。

抱歉!评论已关闭.