DiscuzNT 商品交易插件设计之[线上交易]---支付宝
在上一篇文章中,介绍了商品交易线下交易流程,这一篇则重点介绍一下线上支付宝交易
流程。在开始今天的正文之前,有必要介绍一下关于支付宝交易信息通知的一些内容,因为我
们的商品交易插件使用了其中的一种通知方式。
在支付宝系统中,当交易发生时,根据系统的“处理方式”分为两类:
1.立即返回处理结果,即用户在支付宝页面完成操作后,支付宝将处理结果立即返回给合
作伙伴的下一步操作页面,让用户继续完成整个操作流程。所以调用这类接口时,必须传递参
2.异步返回处理结果,即用户从合作伙伴页面跳转到支付宝页面后,在支付宝完成最后操
作,用户不用再回到合作伙伴页面。这类接口通常是通过通知接口异步获得处理结果。如果需
要异步返回结果,那么必须传递notify_url参数,以指定通知返回的地址。如果不需要异步返
因为第一种方式要将系统的交易通知信息添加到跳转链接并转向回用户页面,给人感觉像
是从一个系统忽然又变到另一个系统,让是感觉怪怪的,另外就是通知的时间有可能不是当时
而是要过上一段时间,导致买卖双方要再回到合作伙伴页面,这样用户体验不是太好。所以我
使用了第二种方式(当然discuz也是用的这一种方式)。
为了便于调用同时也为了支持其他第三方的支付平台,这里进行了接口定义。同时对生成
其中的IPayment是支付接口,目前因为只支持支付宝一种在线支付方式,所以只有一个派
生类:AliPayment。其采用Singleton模式进行类实例化,并提供了创建虚拟商品(digital)
和实物商品交易url链接串的方法,分别是:
这里将商品划分为虚拟和实物商品交易主要是对应了支付定的sdk文档,另外因为这两种
交易在属性上有着部分重叠的地方,因此这里让DigitalTrade作为NormallTrade的父类,并
将其继承到ITrade接口下,而该接口目前暂时为空接口(没有相应的属性和方法)。
要注意的是在这里我将物流信息类分离出来,而这里做的原因是支付宝支持采用多种物
流方式购买商品的行为,其类声明如下所示:
这样,我们在相应的实物交易类中定义这样一个属性来支持多种物流方式的绑定了(详见NormalTrade类):
当然在DigitalTrade类的公有构造函数中对交易配置类(config文件)进行了相应的初始化,
以实现加载合作伙伴认证码及相应的Sign的属性,其代码如下所示:
而有关配置信息类的说明文档详见:discuzNT 商品交易插件设计之用例模型
在获得了相应的字符串数组信息之后,通过一个快速排序方法将这个字符串数组进行排序,
即:QuickSort方法。当然在支付宝官方所给的示例子使用了冒泡算法,虽然能实现功能但效率
上实在是不敢恭维。
快速算法的实现代码如下所示:
这样我们就可以通过传入交易信息来获取支付宝交易URL链接串了。
好了,今天的内容就先到这里,在下一篇文章中,我们将回到在线支付的业务流程中,
继续了解在线支付流程中的支付宝数据回传以及在线进行商品交易时的流程设计方面的内容。
tag:alipay,支付宝,discuznt,online trade,在线交易
作者:代震军,daizhj
原文链接:http://www.cnblogs.com/daizhj/archive/2008/08/18/1270189.html
流程。在开始今天的正文之前,有必要介绍一下关于支付宝交易信息通知的一些内容,因为我
们的商品交易插件使用了其中的一种通知方式。
在支付宝系统中,当交易发生时,根据系统的“处理方式”分为两类:
1.立即返回处理结果,即用户在支付宝页面完成操作后,支付宝将处理结果立即返回给合
作伙伴的下一步操作页面,让用户继续完成整个操作流程。所以调用这类接口时,必须传递参
数return_url(即合作伙伴的下一个操作页面)。如下图所示:
2.异步返回处理结果,即用户从合作伙伴页面跳转到支付宝页面后,在支付宝完成最后操
作,用户不用再回到合作伙伴页面。这类接口通常是通过通知接口异步获得处理结果。如果需
要异步返回结果,那么必须传递notify_url参数,以指定通知返回的地址。如果不需要异步返
回结果,那么可以不用传递notify_url参数。如下图所示:
因为第一种方式要将系统的交易通知信息添加到跳转链接并转向回用户页面,给人感觉像
是从一个系统忽然又变到另一个系统,让是感觉怪怪的,另外就是通知的时间有可能不是当时
而是要过上一段时间,导致买卖双方要再回到合作伙伴页面,这样用户体验不是太好。所以我
使用了第二种方式(当然discuz也是用的这一种方式)。
为了便于调用同时也为了支持其他第三方的支付平台,这里进行了接口定义。同时对生成
支付宝url链接串的操作也进行了相应封装。请先看一下相关的类关系图:
相应的类所在位置如下图所示:
其中的IPayment是支付接口,目前因为只支持支付宝一种在线支付方式,所以只有一个派
生类:AliPayment。其采用Singleton模式进行类实例化,并提供了创建虚拟商品(digital)
和实物商品交易url链接串的方法,分别是:
CreateDigitalGoodsTradeUrl() //虚拟商品
CreateNormalGoodsTradeUrl() //实物商品
CreateNormalGoodsTradeUrl() //实物商品
这里将商品划分为虚拟和实物商品交易主要是对应了支付定的sdk文档,另外因为这两种
交易在属性上有着部分重叠的地方,因此这里让DigitalTrade作为NormallTrade的父类,并
将其继承到ITrade接口下,而该接口目前暂时为空接口(没有相应的属性和方法)。
要注意的是在这里我将物流信息类分离出来,而这里做的原因是支付宝支持采用多种物
流方式购买商品的行为,其类声明如下所示:
/// <summary>
/// 物流信息类
/// </summary>
public class LogisticsInfo
{
private string _logistics_type;
private decimal _logistics_fee;
private string _logistics_payment;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="logistics_type">物流类型: VIRTUAL(虚拟物品),POST(平邮),EMS(EMS),EXPRESS(其他快递公司)</param>
/// <param name="logistics_fee">物流费用, 默认为0</param>
/// <param name="logistics_payment">物流支付类型: SELLER_PAY(卖家支付物流费用,费用不计到总价内),BUYER_PAY(买家支付物流费用,费用需要计到总价内),BUYER_PAY_AFTER_RECEIVE(买家收到货后直接支付给物流公司,费用不用计到总价中)</param>
public LogisticsInfo(string logistics_type, decimal logistics_fee, string logistics_payment)
{
this._logistics_type = logistics_type;
this._logistics_fee = logistics_fee;
this._logistics_payment = logistics_payment;
}
/// <summary>
/// 物流类型
/// Alipay文档类型:string
/// 说明: VIRTUAL:虚拟物品 POST:平邮 EMS:EMS EXPRESS:其他快递公司
/// </summary>
public string Logistics_Type
{
get { return _logistics_type; }
set
{
if (value != null && value.Length > 50)
{
throw new ArgumentOutOfRangeException("无效的 LogisticsType(物流类型)", value, value.ToString());
}
_logistics_type = value;
}
}
/// <summary>
/// 物流费用
/// Alipay文档类型:Number(8,2)
/// 0.00--10000000.00默认为0
/// </summary>
public decimal Logistics_Fee
{
get
{
_logistics_fee = decimal.Round(_logistics_fee, 2);
return _logistics_fee;
}
set
{
if (value < 0.00m || value > 100000000.00m)
{
throw new ArgumentNullException(_logistics_fee.ToString(), "Price(商品单价) 必须为0.01在100000000.00之间");
}
_logistics_fee = value;
}
}
/// <summary>
/// 物流支付类型
/// Alipay文档类型:string
/// 说明: SELLER_PAY 卖家支付(卖家支付物流费用,费用不计到总价内)
/// BUYER_PAY 买家支付(买家支付物流费用,费用需要计到总价内)
/// BUYER_PAY_AFTER_RECEIVE 货到付款(买家收到货后直接支付给物流公司,费用不用计到总价中)
/// </summary>
public string Logistics_Payment
{
get { return _logistics_payment; }
set
{
if (value != null && value.Length > 50)
throw new ArgumentOutOfRangeException("无效的 LogisticsPayment(物流支付类型)", value, value.ToString());
_logistics_payment = value;
}
}
}
/// 物流信息类
/// </summary>
public class LogisticsInfo
{
private string _logistics_type;
private decimal _logistics_fee;
private string _logistics_payment;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="logistics_type">物流类型: VIRTUAL(虚拟物品),POST(平邮),EMS(EMS),EXPRESS(其他快递公司)</param>
/// <param name="logistics_fee">物流费用, 默认为0</param>
/// <param name="logistics_payment">物流支付类型: SELLER_PAY(卖家支付物流费用,费用不计到总价内),BUYER_PAY(买家支付物流费用,费用需要计到总价内),BUYER_PAY_AFTER_RECEIVE(买家收到货后直接支付给物流公司,费用不用计到总价中)</param>
public LogisticsInfo(string logistics_type, decimal logistics_fee, string logistics_payment)
{
this._logistics_type = logistics_type;
this._logistics_fee = logistics_fee;
this._logistics_payment = logistics_payment;
}
/// <summary>
/// 物流类型
/// Alipay文档类型:string
/// 说明: VIRTUAL:虚拟物品 POST:平邮 EMS:EMS EXPRESS:其他快递公司
/// </summary>
public string Logistics_Type
{
get { return _logistics_type; }
set
{
if (value != null && value.Length > 50)
{
throw new ArgumentOutOfRangeException("无效的 LogisticsType(物流类型)", value, value.ToString());
}
_logistics_type = value;
}
}
/// <summary>
/// 物流费用
/// Alipay文档类型:Number(8,2)
/// 0.00--10000000.00默认为0
/// </summary>
public decimal Logistics_Fee
{
get
{
_logistics_fee = decimal.Round(_logistics_fee, 2);
return _logistics_fee;
}
set
{
if (value < 0.00m || value > 100000000.00m)
{
throw new ArgumentNullException(_logistics_fee.ToString(), "Price(商品单价) 必须为0.01在100000000.00之间");
}
_logistics_fee = value;
}
}
/// <summary>
/// 物流支付类型
/// Alipay文档类型:string
/// 说明: SELLER_PAY 卖家支付(卖家支付物流费用,费用不计到总价内)
/// BUYER_PAY 买家支付(买家支付物流费用,费用需要计到总价内)
/// BUYER_PAY_AFTER_RECEIVE 货到付款(买家收到货后直接支付给物流公司,费用不用计到总价中)
/// </summary>
public string Logistics_Payment
{
get { return _logistics_payment; }
set
{
if (value != null && value.Length > 50)
throw new ArgumentOutOfRangeException("无效的 LogisticsPayment(物流支付类型)", value, value.ToString());
_logistics_payment = value;
}
}
}
这样,我们在相应的实物交易类中定义这样一个属性来支持多种物流方式的绑定了(详见NormalTrade类):
/// <summary>
/// 物流信息集合属性,详见<see cref="Discuz.Payment.Alipay.GoodsTradeInfo.LogisticsInfo"/>类
/// </summary>
public LogisticsInfo[] Logistics_Info
{
get { return _logistics_info; }
set
{
if (value != null && value.Length <=0 )
{
throw new ArgumentOutOfRangeException("无效的 物流信息(收货人姓名)", value, value.ToString());
}
_logistics_info = value;
}
}
/// 物流信息集合属性,详见<see cref="Discuz.Payment.Alipay.GoodsTradeInfo.LogisticsInfo"/>类
/// </summary>
public LogisticsInfo[] Logistics_Info
{
get { return _logistics_info; }
set
{
if (value != null && value.Length <=0 )
{
throw new ArgumentOutOfRangeException("无效的 物流信息(收货人姓名)", value, value.ToString());
}
_logistics_info = value;
}
}
当然在DigitalTrade类的公有构造函数中对交易配置类(config文件)进行了相应的初始化,
以实现加载合作伙伴认证码及相应的Sign的属性,其代码如下所示:
public DigitalTrade()
{
TradeConfigInfo tradeConfigInfo = TradeConfigs.GetConfig();
_input_charset = tradeConfigInfo.Alipayconfiginfo.Inputcharset;
_partner = tradeConfigInfo.Alipayconfiginfo.Partner;
_sign = tradeConfigInfo.Alipayconfiginfo.Sign;
TradeConfigs.SaveConfig(tradeConfigInfo);
}
{
TradeConfigInfo tradeConfigInfo = TradeConfigs.GetConfig();
_input_charset = tradeConfigInfo.Alipayconfiginfo.Inputcharset;
_partner = tradeConfigInfo.Alipayconfiginfo.Partner;
_sign = tradeConfigInfo.Alipayconfiginfo.Sign;
TradeConfigs.SaveConfig(tradeConfigInfo);
}
而有关配置信息类的说明文档详见:discuzNT 商品交易插件设计之用例模型
当然具备了这些之后,我们还有必要进一步了解CreateDigitalGoodsTradeUrl及Create
NormalGoodsTradeUrl方法的一些内部信息,因为这两个方法便是我们按支付宝要求创建交易
url链接串的方法,下面以CreateDigitalGoodsTradeUrl为例来说明一下它的工作原理,首先我
们先看一下它的代码:
/// <summary>
/// 构造虚拟商品url
/// </summary>
/// <param name="digitalGoods">虚拟商品信息</param>
/// <param name="key">账户的交易安全校验码(key)</param>
/// <returns></returns>
public string CreateDigitalGoodsTradeUrl(ITrade _goods)
{
DigitalTrade digitalGoods = (DigitalTrade)_goods;
string tradeUrl = ""; //未进行UrlEncode编码的链接参数串
string encodeUrl = "";//进行UrlEncode编码的链接参数串
string[] urlParamArray = GetUrlParam(digitalGoods);
//排序参数
QuickSort(urlParamArray, 0, urlParamArray.Length - 1);
tradeUrl = CreateTradeUrl(urlParamArray);
encodeUrl = CreateEncodeUrl(urlParamArray);
return PayUrl + encodeUrl + string.Format("&sign={0}&sign_type={1}", GetMD5(tradeUrl + digitalGoods.Sign, digitalGoods.Input_Charset), digitalGoods.Sign_Type);
}
/// 构造虚拟商品url
/// </summary>
/// <param name="digitalGoods">虚拟商品信息</param>
/// <param name="key">账户的交易安全校验码(key)</param>
/// <returns></returns>
public string CreateDigitalGoodsTradeUrl(ITrade _goods)
{
DigitalTrade digitalGoods = (DigitalTrade)_goods;
string tradeUrl = ""; //未进行UrlEncode编码的链接参数串
string encodeUrl = "";//进行UrlEncode编码的链接参数串
string[] urlParamArray = GetUrlParam(digitalGoods);
//排序参数
QuickSort(urlParamArray, 0, urlParamArray.Length - 1);
tradeUrl = CreateTradeUrl(urlParamArray);
encodeUrl = CreateEncodeUrl(urlParamArray);
return PayUrl + encodeUrl + string.Format("&sign={0}&sign_type={1}", GetMD5(tradeUrl + digitalGoods.Sign, digitalGoods.Input_Charset), digitalGoods.Sign_Type);
}
在这里,该方法将传入的交易信息(ITrade接口类型)转换成为DigitalTrade类型,然
后通过反射的方式将其属性转换成指定格式的数组,而这个工作交给了GetUrlParam方法。
其代码段如下所示:/// <summary>
/// 反射出指定对象实例的所有属性值
/// </summary>
/// <param name="obj">指定对象实例</param>
/// <returns></returns>
private static string[] GetUrlParam(object obj)
{
PropertyInfo[] propertyInfos = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
string urlParam = "";
foreach (PropertyInfo pi in propertyInfos)
{
if (pi.GetValue(obj, null) != null)
{
if (pi.Name == "Sign" || pi.Name == "Sign_Type")
{
continue;
}
//物流信息时
if (pi.Name == "Logistics_Info")
{
LogisticsInfo[] logisticsInfoArray = ((NormalTrade)obj).Logistics_Info;
int i = 0;
foreach (LogisticsInfo logisticsInfo in logisticsInfoArray)
{
if (logisticsInfo.Logistics_Type != "")
{
//物流参数的下标(第一种物流方式为"",第二种为"_1",第三种为"_2",以此类推)
string orderflag = "";
if (i > 0)
{
orderflag = "_" + i;
}
urlParam += "logistics_type" + orderflag + "=" + logisticsInfo.Logistics_Type + "&logistics_fee" + orderflag + "=" + logisticsInfo.Logistics_Fee + "&logistics_payment" + orderflag + "=" + logisticsInfo.Logistics_Payment +"&";
i++;
}
}
}
else
{
urlParam += pi.Name.ToLower().Replace("input_charset", "_input_charset") + "=" + pi.GetValue(obj, null).ToString() +"&";
}
}
}
if (urlParam.EndsWith("&"))
{
urlParam = urlParam.Substring(0,urlParam.Length-1);
}
return urlParam.Split('&');
}
/// 反射出指定对象实例的所有属性值
/// </summary>
/// <param name="obj">指定对象实例</param>
/// <returns></returns>
private static string[] GetUrlParam(object obj)
{
PropertyInfo[] propertyInfos = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
string urlParam = "";
foreach (PropertyInfo pi in propertyInfos)
{
if (pi.GetValue(obj, null) != null)
{
if (pi.Name == "Sign" || pi.Name == "Sign_Type")
{
continue;
}
//物流信息时
if (pi.Name == "Logistics_Info")
{
LogisticsInfo[] logisticsInfoArray = ((NormalTrade)obj).Logistics_Info;
int i = 0;
foreach (LogisticsInfo logisticsInfo in logisticsInfoArray)
{
if (logisticsInfo.Logistics_Type != "")
{
//物流参数的下标(第一种物流方式为"",第二种为"_1",第三种为"_2",以此类推)
string orderflag = "";
if (i > 0)
{
orderflag = "_" + i;
}
urlParam += "logistics_type" + orderflag + "=" + logisticsInfo.Logistics_Type + "&logistics_fee" + orderflag + "=" + logisticsInfo.Logistics_Fee + "&logistics_payment" + orderflag + "=" + logisticsInfo.Logistics_Payment +"&";
i++;
}
}
}
else
{
urlParam += pi.Name.ToLower().Replace("input_charset", "_input_charset") + "=" + pi.GetValue(obj, null).ToString() +"&";
}
}
}
if (urlParam.EndsWith("&"))
{
urlParam = urlParam.Substring(0,urlParam.Length-1);
}
return urlParam.Split('&');
}
在获得了相应的字符串数组信息之后,通过一个快速排序方法将这个字符串数组进行排序,
即:QuickSort方法。当然在支付宝官方所给的示例子使用了冒泡算法,虽然能实现功能但效率
上实在是不敢恭维。
快速算法的实现代码如下所示:
#region (URL)参数快速排序
/// <summary>
/// 把数组划分为两个部分
/// </summary>
/// <param name="arr">划分的数组</param>
/// <param name="low">数组低端上标</param>
/// <param name="high">数组高端下标</param>
/// <returns></returns>
public static int Partition(string[] strArray, int low, int high)
{
//进行一趟快速排序,返回中心轴记录位置
// arr[0] = arr[low];
string pivot = strArray[low];//把中心轴置于arr[0]
while (low < high)
{
while (low < high && System.String.CompareOrdinal(strArray[high], pivot) >= 0)
--high;
//将比中心轴记录小的移到低端
Swap(ref strArray[high], ref strArray[low]);
while (low < high && System.String.CompareOrdinal(strArray[low], pivot) <= 0)
++low;
Swap(ref strArray[high], ref strArray[low]);
//将比中心轴记录大的移到高端
}
strArray[low] = pivot; //中心轴移到正确位置
return low; //返回中心轴位置
}
public static void Swap(ref string i, ref string j)
{
string t;
t = i;
i = j;
j = t;
}
/// <summary>
/// 快速排序算法
/// </summary>
/// <param name="arr">划分的数组</param>
/// <param name="low">数组低端上标</param>
/// <param name="high">数组高端下标</param>
public static void QuickSort(string[] strArray, int low, int high)
{
if (low <= high - 1)//当 arr[low,high]为空或只一个记录无需排序
{
int pivot = Partition(strArray, low, high);
//左子树
QuickSort(strArray, low, pivot - 1);
//右子树
QuickSort(strArray, pivot + 1, high);
}
}
#endregion
/// <summary>
/// 把数组划分为两个部分
/// </summary>
/// <param name="arr">划分的数组</param>
/// <param name="low">数组低端上标</param>
/// <param name="high">数组高端下标</param>
/// <returns></returns>
public static int Partition(string[] strArray, int low, int high)
{
//进行一趟快速排序,返回中心轴记录位置
// arr[0] = arr[low];
string pivot = strArray[low];//把中心轴置于arr[0]
while (low < high)
{
while (low < high && System.String.CompareOrdinal(strArray[high], pivot) >= 0)
--high;
//将比中心轴记录小的移到低端
Swap(ref strArray[high], ref strArray[low]);
while (low < high && System.String.CompareOrdinal(strArray[low], pivot) <= 0)
++low;
Swap(ref strArray[high], ref strArray[low]);
//将比中心轴记录大的移到高端
}
strArray[low] = pivot; //中心轴移到正确位置
return low; //返回中心轴位置
}
public static void Swap(ref string i, ref string j)
{
string t;
t = i;
i = j;
j = t;
}
/// <summary>
/// 快速排序算法
/// </summary>
/// <param name="arr">划分的数组</param>
/// <param name="low">数组低端上标</param>
/// <param name="high">数组高端下标</param>
public static void QuickSort(string[] strArray, int low, int high)
{
if (low <= high - 1)//当 arr[low,high]为空或只一个记录无需排序
{
int pivot = Partition(strArray, low, high);
//左子树
QuickSort(strArray, low, pivot - 1);
//右子树
QuickSort(strArray, pivot + 1, high);
}
}
#endregion
而排序结序后,通过CreateTradeUrl方法将这个经过排序的字符串数组组合成为我们所需要
的链接串格式,如下所示:
接着调用CreateEncodeUrl方法再次对经过排序的字符串数组进行编码并也转换成所需要的
链接串格式,当然这里的链接串将会是我们在IE地址栏中所出现的那个链接串中的内容。而上面
CreateTradeUrl的链接串则只是为了进行GetMD5加密并获得 sign签名才做的,这部分内容请
参见下面代码段:
encodeUrl = CreateEncodeUrl(urlParamArray);
return PayUrl + encodeUrl + string.Format("&sign={0}&sign_type={1}", GetMD5(tradeUrl + digitalGoods.Sign, digitalGoods.Input_Charset), digitalGoods.Sign_Type);
return PayUrl + encodeUrl + string.Format("&sign={0}&sign_type={1}", GetMD5(tradeUrl + digitalGoods.Sign, digitalGoods.Input_Charset), digitalGoods.Sign_Type);
这样我们就可以通过传入交易信息来获取支付宝交易URL链接串了。
好了,今天的内容就先到这里,在下一篇文章中,我们将回到在线支付的业务流程中,
继续了解在线支付流程中的支付宝数据回传以及在线进行商品交易时的流程设计方面的内容。
tag:alipay,支付宝,discuznt,online trade,在线交易
作者:代震军,daizhj
原文链接:http://www.cnblogs.com/daizhj/archive/2008/08/18/1270189.html