第20章 银行在线支付系统
本章视频教学录像:17分钟
目前,由于电子商务的普及,越来越多的生意人和消费者通过网络进行交易。由于网上支付存在诸多不安全因素,所以支付宝诞生了。支付宝是个交易中介,人们可以通过它进行安全交易。
本章要点(已掌握的在方框中打钩)
□ 了解用户验证的原理
□ 了解银行支付的原理
□ 实现一个简单的支付功能
20.1 系统分析
本节视频教学录像:5分钟
最完美的在线支付莫过于直接从银行或客户的账户那里取得想要支付的金额,但是安全性是各个小站点的软肋。由于各种网络欺诈行为的存在,银行默认各个小站点的网站支付系统是不安全的,转而认证一些第三方作为安全的支付中介,比较流行的支付中介有支付宝、财付通等。其实从银行直接转账和从支付宝进行转账的原理是一样的,我们可通过支付中介完成一次次支付功能。
20.1.1 系统目标
本系统需要实现的目标有以下两点。
⑴了解支付宝在线支付机制。
⑵实现一次在线支付功能。
20.1.2 系统原理
在线支付分为买家直接从银行或账户付款和从第三方(如支付宝)付款两种情况。其中,第一种情况不具通用性,因为各家银行有各种五花八门的支付机制,我们没有必要去研究,我们只需要研究第三方支付中最具普遍性、代表性的支付方式——支付宝即可。
买家要想通过支付宝交易,必须是支付宝的注册用户,这样所有的交易将有据可查;而卖家要想和支付宝做接口,也要注册支付宝,只不过除了注册用户外,还需要开通“商家服务”,这样支付宝就知道买家付的款该支付到哪里,用什么协议为商家服务等。
下图为卖家开通支付宝支付功能时的入口界面。注册成功后,将获取此卖家唯一的商家Id、交易安全码、集成文档等,以备将来开发网站支付功能。
注意:在申请商家服务时会要求选择支付方式,需要在此处设置好,过后无法修改。支付宝的3种支付方式及其区别如下。
1. 标准双接口
商家在自己的网站集成完该接口后,买家在商家网站下订单,提交到支付宝收银台后,由买家选择是使用担保交易还是即时到账交易(优化了交易流程,有利于缩短商家资金周转周期)。
2. 担保交易
商家在自己的网站集成完该接口后,买家在商家网站下订单,提交到支付宝收银台后,买家只能选择担保交易进行交易。交易流程:买家付款→卖家发货→买家确认收货→交易成功。由支付宝提供中介担保服务。
3. 即时到账
商家集成该功能后,买家下完订单,付款成功后,款项立刻到达商家的支付宝账户。不需要买家确认收货、卖家发货环节,主要用于虚拟物品交易类型。
进行根网站的集成前,可以先在上图中单击【合同列表】【操作】【下载集成文档】,打开下面所示页面,根据不同的页面提供的主题,定制适合自己网站的支付功能。
本章将采用官方论坛提供的集成示例代码,简单改造之后进行讲解。读者可以下载上面页面中的【标准双接口开发文档及其代码实例】【.net(GBK)(2.0)实物标准代码实例】。
20.1.3 技术要点
支付宝通过带参数的URL获取商家的支付信息,根据卖家网站的URL链接中提供的信息进行支付后,再反馈给卖家和买家相关的信息。因此,支付的要点便是卖家的网站要提供完整的带参数的URL到支付宝,然后等支付宝返回支付结果即可。为此,我们先封装一个生产URL的类。
在App_Code下面有个类,叫做Alipay.cs,此文件是支付功能的核心组件,其代码如下(代码15-1.txt)。
01 using System;
02 using System.Data;
03 using System.Configuration;
04 using System.Web;
05 using System.Web.Security;
06 using System.Web.UI;
07 using System.Web.UI.WebControls;
08 using System.Web.UI.WebControls.WebParts;
09 using System.Web.UI.HtmlControls;
10 using System.Text;
11 using System.Security.Cryptography;
12 namespace Gateway
13 {
14 public class AliPay
15 {
16 ///<summary>
17 ///与ASP兼容的MD5加密算法
18 ///< /summary>
19 public static string GetMD5(string s, string _input_charset)
20 {
21 MD5 md5= new MD5CryptoServiceProvider();
22 byte[] t=md5.ComputeHash(Encoding.GetEncoding(_input_charset).GetBytes(s));
23 StringBuilder sb= new StringBuilder(32);
24 for (int i= 0; i< t.Length; i++)
25 {
26 sb.Append(t[i].ToString("x").PadLeft(2, '0'));
27 }
28 return sb.ToString();
29 }
30 ///<summary>
31 ///冒泡排序法
32 ///< /summary>
33 public static string[]BubbleSort(string[] r)
34 {
35 int i, j; //交换标志
36 string temp;
37 bool exchange;
38 for (i= 0; i< r.Length; i++) //最多做R.Length-1趟排序
39 {
40 exchange= false; //本趟排序开始前,交换标志应为假
41 for (j= r.Length - 2; j>= i; j--)
42 {
43 if (System.String.CompareOrdinal(r[j+ 1], r[j])< 0) //交换条件
44 {
45 temp= r[j+ 1];
46 r[j+ 1]= r[j];
47 r[j]= temp;
48 exchange= true; //发生了交换,故将交换标志置为真
49 }
50 }
51 if (!exchange) //本趟排序未发生交换,提前终止算法
52 {
53 break;
54 }
55 }
56 return r;
57 }
58 ///<summary>
59 ///CreatUrl
60 ///< /summary>
61 public string CreatUrl(
62 string gateway, string service, string partner, string sign_type,
63 string out_trade_no, string subject, string body, string payment_type,
64 string total_fee, string show_url, string seller_email, string key,
65 string return_url, string _input_charset, string notify_url
66 )
67 {
68 int i;
69
70 //构造数组;
71 string[] Oristr={
72 "service="+service,
73 "partner="+partner,
74 "subject="+ subject,
75 "body="+body,
76 "out_trade_no="+ out_trade_no,
77 "price="+ total_fee,
78 "show_url="+ show_url,
79 "payment_type="+payment_type,
80 "seller_email="+ seller_email,
81 "notify_url="+ notify_url,
82 "_input_charset="+_input_charset,
83 "return_url="+ return_url,
84 "discount=-0.01",
85 "quantity=1",
86 "logistics_type=EXPRESS",
87 "logistics_fee=0" ,
88 "logistics_payment=BUYER_PAY",
89 "logistics_type_1=POST",
90 "logistics_fee_1=0",
91 "logistics_payment_1=BUYER_PAY"
92 };
93
94 //进行排序
95 string[] Sortedstr=BubbleSort(Oristr);
96 //构造待md5摘要字符串
97 StringBuilder prestr= newStringBuilder();
98 for (i= 0; i<Sortedstr.Length; i++)
99 {
100 if (i==Sortedstr.Length - 1)
101 {
102 prestr.Append(Sortedstr[i]);
103 }
104 else
105 {
106 prestr.Append(Sortedstr[i]+ "&");
107 }
108 }
109 prestr.Append(key);
110 //生成Md5摘要
111 string sign=GetMD5(prestr.ToString(), _input_charset);
112 //构造支付Url
113 char[] delimiterChars= { '='};
114 StringBuilder parameter= new StringBuilder();
115 parameter.Append(gateway);
116 for (i= 0; i<Sortedstr.Length; i++)
117 {
118 parameter.Append(Sortedstr[i].Split(delimiterChars)[0] + "=" + HttpUtility.UrlEncode(Sortedstr[i].Split(delimiterChars)[1])+ "&");
119 }
120 parameter.Append("sign="+ sign+ "&sign_type="+ sign_type);
121
122 //返回支付Url
123 return parameter.ToString();
124 }
125 }
【代码详解】
构造URL的方法虽然很长,但是没有什么技术含量,就是一个简单的支付宝接口字符串的拼接。
20.2 系统设计
本节视频教学录像:7分钟
本节我们来了解支付宝系统功能的实现。
20.2.1 设计订单提交功能
我们先来设计系统的页面“SubmitPage.aspx”,如图所示。
页面说明如下。
⑴页面上方是商品支付时需要的信息,其中最关键的两个参数是“总金额”和“卖家账号”。
⑵页面下方是支付宝商家相关参数,其中“服务参数”中有3个选项,其含义如表所示。
⑶本页面通过获取用户数据,并调用上一节制作的Alipay.cs中的核心方法CreateURL,来生成支付宝的链接,并将调用此链接。此页面的完整后台代码如下(代码15-2.txt)。
01 using System;
02 using System.Data;
03 using System.Configuration;
04 using System.Collections;
05 using System.Web;
06 using System.Web.Security;
07 using System.Web.UI;
08 using System.Web.UI.WebControls;
09 using System.Web.UI.WebControls.WebParts;
10 using System.Web.UI.HtmlControls;
11 using Gateway;
12 public partial class SubmitPage :System.Web.UI.Page
13 {
14 protected void Page_Load(object sender,EventArgs e)
15 {
16 }
17 protected void Button1_Click(object sender,EventArgs e)
18 {
19 //按时构造订单号
20 System.DateTime currentTime= new System.DateTime();
21 currentTime=System.DateTime.Now;
22 //获取适合使用习惯的日期,例如2006-09-13 14:20
23 string out_trade_no= currentTime.ToString("g");
24 //替换日期中的特殊字符
25 out_trade_no= out_trade_no.Replace("-", "");
26 out_trade_no= out_trade_no.Replace(":", "");
27 out_trade_no= out_trade_no.Replace(" ", "");
28 //业务参数赋值
29 //支付接口
30 stringgateway= T_gateway.Text;
31 //服务接口名称,此处采用测试默认值
32 string service= T_service.Text;
33 //合作伙伴 ID。注册为支付宝用户后获取
34 string partner= T_partner.Text;
35 //加密类型
36 string sign_type= T_sign_type.Text;
37 //商品名称
38 string subject= T_subject.Text;
39 //商品描述
40 string body= T_body.Text;
41 //支付类型此处默认为商品购买,具体类型可参考下载的文档
42 string payment_type= T_payment_type.Text;
43 //总金额0.01~50000.00
44 string total_fee= T_total_fee.Text;
45 //商品的展示地址
46 string show_url= T_show_url.Text;
47 //卖家账号
48 string seller_email= T_seller_email.Text;
49 //partner账户的支付宝安全校验码
50 string key= T_key.Text;
51 //服务器返回接口
52 string return_url= T_return_url.Text;
53 //服务器通知返回接口
54 string notify_url= T_notify_url.Text;
55 //编码格式
56 string _input_charset= T_inputchatset.Text;
57 //生成一个支付对象
58 AliPay ap= new AliPay();
59 //根据网关校验,并返回完成地址
60 string aliay_url= ap.CreatUrl(
61 gateway, service, partner,
62 sign_type, out_trade_no, subject,
63 body, payment_type, total_fee,
64 show_url, seller_email, key,
65 return_url, _input_charset,notify_url );
66 //导航到支付宝交付页面
67 Response.Redirect(aliay_url);
68 }
69 }
当支付宝链接创建并被导航之后,所有的支付过程操作都与本站无关,所有的操作交由支付宝处理。现在我们需要异步地等待支付的结果,并将结果在特定的页面上显示给用户。为此可建立一个页面,叫做Notice.aspx。
根据上面封装的方法,支付成功后会打开return_url(本例即Default.aspx),否则提示通知显示在另一个页面notify_url(本例即Notice.aspx)上。
下面分别建立两个页面,叫作Default.aspx和Notice.aspx,分别作为Return_url和Notify_url来处理相关反馈。
20.2.2 支付成功后的处理页面
打开Default.aspx.cs页面,所有代码如下(代码15-3.txt)。
01 public partial class _Default :System.Web.UI.Page
02 {
03 protected void Page_Load(object sender,EventArgs e)
04 {
05 //支付宝的网关
06 string alipayNotifyURL= "https://www.alipay.com/cooperate/gateway.do?";
07 //用户注册支付宝时生成的校验码(必须填写自己的)
08 string key= "xxxxxxx";
09 //页面编码格式
10 string _input_charset= "utf-8";
11 //用户注册支付宝时生成的合作伙伴 id(必须填写自己的)
12 string partner= "xxxxxx";
13 alipayNotifyURL= alipayNotifyURL+ "service=create_digital_goods_trade_p"+ "&partner="+partner+ "¬ify_id="+Request.QueryString["notify_id"];
14
15 //获取支付宝ATN返回结果,true是正确的订单信息,false是无效的
16 string responseTxt=Get_Http(alipayNotifyURL, 120000);
17 int i;
18 NameValueCollection coll;
19 //在集合中装载返回信息
20 coll=Request.QueryString;
21 //将所有的键值保存在数组中
22 String[] requestarr= coll.AllKeys;
23 //进行排序
24 string[] Sortedstr=BubbleSort(requestarr);
25 for (i= 0; i<Sortedstr.Length; i++)
26 {
27 Response.Write("Form: " + Sortedstr[i] + "=" + Request.QueryString[Sortedstr[i]] +"<br>");
28 }
29 //构造待md5摘要字符串
30 StringBuilder prestr= new StringBuilder();
31 for (i= 0; i<Sortedstr.Length; i++)
32 {
33 if (Request.Form[Sortedstr[i]] != ""&&Sortedstr[i] != "sign"&&Sortedstr[i] != "sign_type")
34 {
35 if (i==Sortedstr.Length - 1)
36 {
37 prestr.Append(Sortedstr[i]+ "="+Request.QueryString[Sortedstr[i]]);
38 }
39 else
40 {
41 prestr.Append(Sortedstr[i]+ "="+Request.QueryString[Sortedstr[i]]+ "&");
42 }
43 }
44 }
45 prestr.Append(key);
46 //生成Md5摘要
47 string mysign=GetMD5(prestr.ToString(), _input_charset);
48 string sign=Request.QueryString["sign"];
49 //测试返回的结果
50 Response.Write(prestr.ToString());
51 //验证支付发过来的消息,签名是否正确
52 if (mysign== sign && responseTxt== "true")
53 {
54 //此时可以更新网站的数据,比如商品的减少,等等
55 Response.Write("success"); //返回给支付宝消息,成功
56 }
57 else
58 {
59 //Response.Write("------------------------------------------");
60 //Response.Write("<br>Result:responseTxt="+ responseTxt);
61 //Response.Write("<br>Result:mysign="+mysign);
62 //Response.Write("<br>Result:sign="+ sign);
63 Response.Write("fail");
64 }
65 }
66 public static stringGetMD5(string s, string _input_charset)
67 {
68 ///<summary>
69 ///与ASP兼容的MD5加密算法
70 ///< /summary>
71 MD5 md5= newMD5CryptoServiceProvider();
72 byte[] t=md5.ComputeHash(Encoding.GetEncoding(_input_charset).GetBytes(s));
73 StringBuilder sb= new StringBuilder(32);
74 for (int i= 0; i< t.Length; i++)
75 {
76 sb.Append(t[i].ToString("x").PadLeft(2, '0'));
77 }
78 return sb.ToString();
79 }
80 public static string[]BubbleSort(string[] r)
81 {
82 ///<summary>
83 ///冒泡排序法
84 ///< /summary>
85 //交换标志
86 int i, j;
87 string temp;
88 bool exchange;
89 //最多做R.Length-1趟排序
90 for (i= 0; i< r.Length; i++)
91 {
92 //本趟排序开始前,交换标志应为假
93 exchange= false;
94 for (j= r.Length - 2; j>= i; j--)
95 {
96 //交换条件
97 if (System.String.CompareOrdinal(r[j+ 1], r[j])< 0)
98 {
99 temp= r[j+ 1];
100 r[j+ 1]= r[j];
101 r[j]= temp;
102 //发生了交换,故将交换标志置为真
103 exchange= true;
104 }
105 }
106 //本趟排序未发生交换,提前终止算法
107 if (!exchange)
108 {
109 break;
110 }
111 }
112 return r;
113 }
114 //获取远程服务器ATN结果
115 public String Get_Http(String a_strUrl, int timeout)
116 {
117 string strResult;
118 try
119 {
120 //创建访问页面
121 HttpWebRequestmyReq= (HttpWebRequest)HttpWebRequest.Create(a_strUrl);
122 myReq.Timeout= timeout;
123 HttpWebResponseHttpWResp= (HttpWebResponse)myReq.GetResponse();
124 //获取页面返回数据流
125 StreammyStream=HttpWResp.GetResponseStream();
126 StreamReader sr= newStreamReader(myStream,Encoding.Default);
127 StringBuilder strBuilder= newStringBuilder();
128 //获取内容
129 while (-1 != sr.Peek())
130 {
131 strBuilder.Append(sr.ReadLine());
132 }
133 strResult= strBuilder.ToString();
134 }
135 catch (Exception exp)
136 {
137 strResult= "错误:"+ exp.Message;
138 }
139 return strResult;
140 }
141 }
20.2.3 支付返回通知提示的处理页面
本页面是专门用于处理支付不成功的页面,其处理数据的方式和Default.aspx基本一致,但稍有不同。复制Default.aspx页面,将其重命名为Notice.aspx页面,对Page_Load方法稍作修改即可,其余代码不动。
Page_Load方法所有的代码如下(代码15-4.txt)。
01 protected void Page_Load(object sender,EventArgs e)
02 {
03 string alipayNotifyURL= "https://www.alipay.com/cooperate/gateway.do?";
04 //partner合作伙伴 id(必须填写)
05 string partner= "xxxxxxxxxx";
06 //partner的对应交易安全校验码(必须填写)
07 string key= "xxxxxxxx";
08 alipayNotifyURL= alipayNotifyURL+ "service=create_digital_goods_trade_p"+ "&partner="+partner+ "¬ify_id="+Request.Form["notify_id"];
09 //获取支付宝ATN返回结果,true是正确的订单信息,false是无效的
10 string responseTxt=Get_Http(alipayNotifyURL, 120000);
11 int i;
12 NameValueCollection coll;
13 //在集合中装载返回信息
14 coll=Request.Form;
15 //将所有的键值保存在数组中
16 String[] requestarr= coll.AllKeys;
17 //进行排序
18 string[] Sortedstr=BubbleSort(requestarr);
19 //构造待md5摘要字符串
20 string prestr= "";
21 for (i= 0; i<Sortedstr.Length; i++)
22 {
23 if (Request.Form[Sortedstr[i]] != ""&&Sortedstr[i] != "sign"&&Sortedstr[i] != "sign_type")
24 {
25 if (i==Sortedstr.Length - 1)
26 {
27 prestr=prestr+Sortedstr[i]+ "="+Request.Form[Sortedstr[i]];
28 }
29 else
30 {
31 prestr=prestr+Sortedstr[i]+ "="+Request.Form[Sortedstr[i]]+ "&";
32 }
33 }
34 }
35 prestr=prestr+ key;
36 string mysign=GetMD5(prestr);
37 string sign=Request.Form["sign"];
38 //验证支付发过来的消息,签名是否正确
39 if (mysign== sign && responseTxt== "true")
40 {
41 //判断支付状态
42 if (Request.Form["trade_status"]== "WAIT_SELLER_SEND_GOODS")
43 {
44 //更新自己数据库的订单语句
45 //返回给支付宝消息,成功
46 Response.Write("success");
47 }
48 else
49 {
50 Response.Write("fail");
51 }
52 }
53 }
20.2.4 关闭数据库连接
在系统开始统计时要连接数据库,在统计结束时要关闭此连接。在20.1.3小节中的代码后面输入以下代码,用于连接和关闭数据库(代码15-5.txt)。
01 private void OpenConnection()
02 {
03 if (conn== null)
04 conn= new SqlConnection(strConn);
05 if (conn.State==ConnectionState.Closed)
06 conn.Open();
07 }
08 private void CloseConnection()
09 {
10 if (conn.State !=ConnectionState.Closed)
11 conn.Close();
12 }
13 }
20.3 运行系统
本节视频教学录像:2分钟
系统设计好了,下面来看系统运行的效果。
⑴将SubmitPage.aspx设置为本站的起始页。
⑵按【F5】键开始执行程序,先检查SubmitPage 页面上的“合作商”和“安全校验码”是否正确。
⑶检查完毕,在【服务参数】下拉列表中选择【create_partner_trade_by_buyer】,单击【支付宝付款】按钮,打开如图所示的链接页面。
⑷确认信息后,会看到支付选项:直接到银行支付还是登录支付宝再支付。用户可以根据自己的需要选择。此处选择通过支付宝支付,输入用户名、密码,然后单击【确认购买】按钮,会显示如图所示界面。
⑸【确认】地址后,来到支付环节,读者可以根据自己的实际情况选择支付方式。我们选择通过【网上银行付款】,界面如图所示。
⑹输入支付密码并【确认无误,付款】后,会提示支付成功,并立即返回商户提供的Return URL,如果支付失败,会返回失败页面。此处的界面显示如图所示,说明支付成功。
⑺用卖家账户登录支付宝可以查看到交易状态,如图所示。
⑻在交易列表中单击【发货】,可以看到交易详细信息,如图所示。
20.4 在我的网站中运用本系统
本节视频教学录像:1分钟
根据上一节运行的系统,我们得到一个正确的返回值,但是还有一种支付可能,就是用户单击【确认无误,付款】后,服务器端扣款成功,但却掉线了,此时卖家得不到支付反馈信息,怎么办?我们还有一个Notice页面没有测试,它的作用便是处理支付宝和卖家服务器之间的支付后事。支付宝会在24小时之内分6~10次将订单信息返回客户指定的这个URL,直到支付宝捕获success。
用户不要使用支付宝余额支付,根据自己的情况选择某种银行卡支付,等银行卡支付成功后便结束过程,故意使支付结果未反馈给服务器,查看结果。
注意
要进行此操作,不可以是本次服务器的调试,需要将写的代码发布到一个公网服务器上。
20.5 开发过程中的常见问题及解决方式
本节视频教学录像:2分钟
1. 哪里可以下载到官方开发文档及官方实例代码?
官方开发文档入口:登录支付宝,依次选择【商家服务】【合同列表】【下载集成文档】。打开此页面可以下载以下文件。
⑴快速付款接口文档及实例;
⑵担保交易接口文档及实例;
⑶双接口交易文档及实例。
2. 测试支付时是用真金白银进行测试的,很花钱,怎么办?
测试网站时可以使用0.01元来测试,系统会自动去掉一分钱折扣,这样就可以做到不用付款。但是往往需要设置成0.02元,这对于测试真实的交易是有益的。
3. 测试链接到支付宝时总是出现各种报错,怎么办?
首先不要怀疑是支付宝的错误,因为支付宝作为安全而稳定的支付中介广为使用,一般来说都是自己页面参数的设置有问题。为此,建议出错时仔细阅读接口文档。