一.回顾
支付宝插件源码下载地址:点击下载
上篇介绍了使用支付宝插件进行支付,全额退款,部分退款还有插件的多店铺配置,本文介绍下如何实现的。
二.前期准备
插件主要有3个功能:
- 多店铺插件配置
- 支付功能
- 退款功能
数据库支持:
- 新增dbl_PaymentInfo表保存支付记录。
- 新增dbl_RefundInfo表保存退款记录。
其他准备:
- 支付宝即时到账PID和MD5秘钥Key,可通过支付宝开放平台获取。
三.流程规划
- 插件安装卸载流程
2. 支付流程
3. 退款流程
四.创建项目
1. 新建类库项目,名称为 DaBoLang.Nop.Plugin.Payments.AliPay,位置放在Plugins下
2. 点击项目快捷键Alt+Enter进入项目属性,
设置输出路径为..\..\Presentation\Nop.Web\Plugins\DaBoLang.Payments.AliPay\
3. 安装相关包:项目根目录新建packages.config文件,内容如下
1 <?xml version="1.0" encoding="utf-8"?> 2 <packages> 3 <package id="Autofac" version="4.4.0" targetFramework="net451" /> 4 <package id="Microsoft.AspNet.Mvc" version="5.2.3" targetFramework="net451" /> 5 <package id="Microsoft.AspNet.Razor" version="3.2.3" targetFramework="net451" /> 6 <package id="Microsoft.AspNet.WebPages" version="3.2.3" targetFramework="net451" /> 7 <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net451" /> 8 </packages>
打开【工具】【NuGet 包管理器】【程序包管理器控制台】,
输入下边命令更新包: Update-Package -ProjectName 'DaBoLang.Nop.Plugin.Payments.AliPay' -Reinstall
最后添加 Nop.Core Nop.Data Nop.Services Nop.Web.FrameWork 项目引用
4. 创建插件描述文件:根目录新建Description.txt文件 用于插件描述,写入内容如下:
1 Group: Payment methods 2 FriendlyName: 支付宝 3 SystemName: DaBoLang.Payments.AliPay 4 Version: 1.00 5 SupportedVersions: 3.90 6 Author: 大波浪 7 DisplayOrder: 1 8 FileName: DaBoLang.Nop.Plugin.Payments.AliPay.dll 9 Description: 支付宝 即时到账 插件,支持支付,退款
5. 创建项目目录,如果你已经下载了插件项目参考项目目录如下:
- Alipay 支付宝即时到账相关
- Controllers 控制器文件夹
- Data 数据库相关
- Domain 实体类
- Models 模型
- Services 服务接口
- Views 视图
- AliPayPaymentProcessor.cs 支付插件实现类
- AliPayPaymentSettings.cs 支付宝即时到账配置类
- DependencyRegistrar.cs 依赖注入扩展类
- Description.txt 插件描述文件
- RouteProvider.cs 路由注册类
五.创建数据库
项目我们已经创建好了。记下来我们创建付款记录表和退款记录表用于保存相关记录。nop使用的是ORM框架为Entity Framework框架,我们使用Code First模式来创建。
1. 在Domain文件夹创建PaymentInfo类用于保存付款信息,RefundInfo类用于保存退款信息。
nop中的实体类需要继承 Nop.Core.BaseEntity 抽象方法。
2. Data文件夹中创建实体与数据库的映射关系配置
nop中映射关系配置需要继承 Nop.Data.Mapping.NopEntityTypeConfiguration<T> 接口,T泛型为映射的实体类
该接口也是继承EntityTypeConfiguration<T>接口的
PaymentInfoMap类关联PaymentInfo实体类,
构造函数中通过this.ToTable("dbl_PaymentInfo")指定数据库表明,this.HasKey(x => x.Id);指定主键
1 using DaBoLang.Nop.Plugin.Payments.AliPay.Domain; 2 using Nop.Data.Mapping; 3 4 namespace DaBoLang.Nop.Plugin.Payments.AliPay.Data 5 { 6 /// <summary> 7 /// 命名空间:DaBoLang.Nop.Plugin.Payments.AliPay.Data 8 /// 名 称:PaymentInfoMap 9 /// 功 能:实体映射 10 /// 详 细:支付表映射 11 /// 作 者:大波浪 12 /// </summary> 13 public partial class PaymentInfoMap : NopEntityTypeConfiguration<PaymentInfo> 14 { 15 public PaymentInfoMap() 16 { 17 this.ToTable("dbl_PaymentInfo"); 18 this.HasKey(x => x.Id); 19 } 20 } 21 }
RefundInfoMap则将RefundInfo实体类与dbl_RefundInfo表进行关联。
this.Ignore(x=>x.RefundStatus); 表示忽略RefundStatus属性,忽略的属性在创建数据时不会创建该属性字段。
1 using DaBoLang.Nop.Plugin.Payments.AliPay.Domain; 2 using Nop.Data.Mapping; 3 4 namespace DaBoLang.Nop.Plugin.Payments.AliPay.Data 5 { 6 /// <summary> 7 /// 命名空间:DaBoLang.Nop.Plugin.Payments.AliPay.Data 8 /// 名 称:RefundInfoMap 9 /// 功 能:实体映射 10 /// 详 细:退款记录映射 11 /// </summary> 12 public partial class RefundInfoMap : NopEntityTypeConfiguration<RefundInfo> 13 { 14 public RefundInfoMap() 15 { 16 this.ToTable("dbl_RefundInfo"); 17 this.HasKey(x => x.Id); 18 this.Ignore(x=>x.RefundStatus); 19 } 20 } 21 }
3. 创建数据库上下文
我们已经创建好2个表的实体类和映射关系,接下来我们需要创建DbContext数据库上下文。
在Data目录下创建 AliPayObjectContext类,继承DbContext类和Nop.Data.IDbContext接口
重写OnModelCreating方法,将Map配置添加到EF中,
提示:Nop.Data.NopObjectContext是Nop默认上下文可供,这里也可以使用反射机制自动添加配置,当然需要再提取一次Map接口,有兴趣的朋友自行扩展。
1 protected override void OnModelCreating(DbModelBuilder modelBuilder) 2 { 3 modelBuilder.Configurations.Add(new PaymentInfoMap()); 4 modelBuilder.Configurations.Add(new RefundInfoMap()); 5 6 //disable EdmMetadata generation 7 //modelBuilder.Conventions.Remove<IncludeMetadataConvention>(); 8 base.OnModelCreating(modelBuilder); 9 }
添加Install()方法创建表,Uninstall()方法删除表。
1 /// <summary> 2 /// 安装 3 /// </summary> 4 public void Install() 5 { 6 //创建表 7 var dbScript = CreateDatabaseScript(); 8 Database.ExecuteSqlCommand(dbScript); 9 SaveChanges(); 10 } 11 12 /// <summary> 13 /// 卸载 14 /// </summary> 15 public void Uninstall() 16 { 17 //删除表 18 var tableName = this.GetTableName<PaymentInfo>(); 19 this.DropPluginTable(tableName); 20 tableName = this.GetTableName<RefundInfo>(); 21 this.DropPluginTable(tableName); 22 }
完整的代码如下
4. 添加EfStartUpTask类用于启动时初数据库进行初始化
1 using System.Data.Entity; 2 using Nop.Core.Infrastructure; 3 4 namespace DaBoLang.Nop.Plugin.Payments.AliPay.Data 5 { 6 /// <summary> 7 /// 命名空间:DaBoLang.Nop.Plugin.Payments.AliPay.Data 8 /// 名 称:EfStartUpTask 9 /// 功 能:启动任务 10 /// 详 细:启动时数据库初始化从不创建数据库 11 /// 版 本:1.0.0.0 12 /// 作 者:大波浪 13 /// 联系方式:http://www.cnblogs.com/yaoshangjin 14 /// 说 明: 15 /// </summary> 16 public class EfStartUpTask : IStartupTask 17 { 18 public void Execute() 19 { 20 //It's required to set initializer to null (for SQL Server Compact). 21 //otherwise, you'll get something like "The model backing the 'your context name' context has changed since the database was created. Consider using Code First Migrations to update the database" 22 Database.SetInitializer<AliPayObjectContext>(null); 23 } 24 25 public int Order 26 { 27 //ensure that this task is run first 28 get { return 0; } 29 } 30 } 31 } 32
到这里数据库相关类已经创建完毕,但是还不能使用,我们需要将这些类注册到依赖注入框架中。
5. 添加DependencyRegistrar类用于依赖关系注册
1 using Autofac; 2 using Autofac.Core; 3 using DaBoLang.Nop.Plugin.Payments.AliPay.Data; 4 using DaBoLang.Nop.Plugin.Payments.AliPay.Domain; 5 using DaBoLang.Nop.Plugin.Payments.AliPay.Services; 6 using Nop.Core.Configuration; 7 using Nop.Core.Data; 8 using Nop.Core.Infrastructure; 9 using Nop.Core.Infrastructure.DependencyManagement; 10 using Nop.Data; 11 using Nop.Web.Framework.Mvc; 12 13 namespace DaBoLang.Nop.Plugin.Payments.AliPay 14 { 15 /// <summary> 16 /// 命名空间:DaBoLang.Nop.Plugin.Payments.AliPay 17 /// 名 称:DependencyRegistrar 18 /// 功 能:框架 19 /// 详 细:注册 20 /// 版 本:1.0.0.0 21 /// 作 者:大波浪 22 /// 联系方式:http://www.cnblogs.com/yaoshangjin 23 /// 说 明: 24 /// </summary> 25 public class DependencyRegistrar : IDependencyRegistrar 26 { 27 /// <summary> 28 /// Register services and interfaces 29 /// </summary> 30 /// <param name="builder">Container builder</param> 31 /// <param name="typeFinder">Type finder</param> 32 /// <param name="config">Config</param> 33 public virtual void Register(ContainerBuilder builder, ITypeFinder typeFinder, NopConfig config) 34 { 35 //数据库上下文 36 this.RegisterPluginDataContext<AliPayObjectContext>(builder, "nop_object_context_alipay"); 37 38 //为Repository注册数据库上下文 39 builder.RegisterType<EfRepository<PaymentInfo>>() 40 .As<IRepository<PaymentInfo>>() 41 .WithParameter(ResolvedParameter.ForNamed<IDbContext>("nop_object_context_alipay")) 42 .InstancePerLifetimeScope(); 43 44 builder.RegisterType<EfRepository<RefundInfo>>() 45 .As<IRepository<RefundInfo>>() 46 .WithParameter(ResolvedParameter.ForNamed<IDbContext>("nop_object_context_alipay")) 47 .InstancePerLifetimeScope(); 48 49 50 //注册支付记录服务 51 builder.RegisterType<PaymentInfoService>().As<IPaymentInfoService>().InstancePerLifetimeScope(); 52 //注册退款记录服务 53 builder.RegisterType<RefundInfoService>().As<IRefundInfoService>().InstancePerLifetimeScope(); 54 } 55 56 /// <summary> 57 /// Order of this dependency registrar implementation 58 /// </summary> 59 public int Order 60 { 61 get { return 1; } 62 } 63 } 64 } 65
好了样数据库相关类就完成了。
六.插件安装、卸载
现在我们就可以编写支付插件类了,首先创建AliPayPaymentProcessor类,该类继承BasePlugin抽象类,同时继承IPaymentMethod接口。
nop支付插件需要继承Nop.Services.Payments.IpaymentMethod接口,该接口提供了用于支付相关的方法。接口说明请看下边代码注释(理解不正确的地方请留言指正)
1 namespace Nop.Services.Payments 2 { 3 /// <summary> 4 /// 提供了一个接口用于创建支付网关和方法 5 /// </summary> 6 public partial interface IPaymentMethod : IPlugin 7 { 8 #region Methods 9 10 /// <summary> 11 /// 付款处理 12 /// </summary> 13 /// <param name="processPaymentRequest">Payment info required for an order processing</param> 14 /// <returns>Process payment result</returns> 15 ProcessPaymentResult ProcessPayment(ProcessPaymentRequest processPaymentRequest); 16 17 /// <summary> 18 /// 请求付款流程(需要重定向到一个第三方的支付网关使用的URL) 19 /// </summary> 20 /// <param name="postProcessPaymentRequest">Payment info required for an order processing</param> 21 void PostProcessPayment(PostProcessPaymentRequest postProcessPaymentRequest); 22 23 /// <summary> 24 /// 是否应该隐藏支付方式,例如购物车内商品不需要配送 25 /// <param name="cart">Shoping cart</param> 26 /// <returns>true - hide; false - display.</returns> 27 bool HidePaymentMethod(IList<ShoppingCartItem> cart); 28 29 /// <summary> 30 /// 额外费用 31 /// </summary> 32 /// <param name="cart">Shoping cart</param> 33 /// <returns>Additional handling fee</returns> 34 decimal GetAdditionalHandlingFee(IList<ShoppingCartItem> cart); 35 36 /// <summary> 37 /// 跟踪付款 38 /// </summary> 39 /// <param name="capturePaymentRequest">Capture payment request</param> 40 /// <returns>Capture payment result</returns> 41 CapturePaymentResult Capture(CapturePaymentRequest capturePaymentRequest); 42 43 /// <summary> 44 /// 退款 45 /// </summary> 46 /// <param name="refundPaymentRequest">Request</param> 47 /// <returns>Result</returns> 48 RefundPaymentResult Refund(RefundPaymentRequest refundPaymentRequest); 49 50 /// <summary> 51 /// Voids a payment 52 /// </summary> 53 /// <param name="voidPaymentRequest">Request</param> 54 /// <returns>Result</returns> 55 VoidPaymentResult Void(VoidPaymentRequest voidPaymentRequest); 56 57 /// <summary> 58 /// 定期支付 59 /// </summary> 60 /// <param name="processPaymentRequest">Payment info required for an order processing</param> 61 /// <returns>Process payment result</returns> 62 ProcessPaymentResult ProcessRecurringPayment(ProcessPaymentRequest processPaymentRequest); 63 64 /// <summary> 65 /// 取消定期支付 66 /// </summary> 67 /// <param name="cancelPaymentRequest">Request</param> 68 /// <returns>Result</returns> 69 CancelRecurringPaymentResult CancelRecurringPayment(CancelRecurringPaymentRequest cancelPaymentRequest); 70 71 /// <summary> 72 /// 订单创建后但支付未成功,是否支持重复支付 73 /// </summary> 74 /// <param name="order">Order</param> 75 /// <returns>Result</returns> 76 bool CanRePostProcessPayment(Order order); 77 78 /// <summary> 79 /// 配置路由 80 /// </summary> 81 /// <param name="actionName">Action name</param> 82 /// <param name="controllerName">Controller name</param> 83 /// <param name="routeValues">Route values</param> 84 void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues); 85 86 /// <summary> 87 /// 支付信息路由 88 /// </summary> 89 /// <param name="actionName">Action name</param> 90 /// <param name="controllerName">Controller name</param> 91 /// <param name="routeValues">Route values</param> 92 void GetPaymentInfoRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues); 93 94 Type GetControllerType(); 95 96 #endregion 97 98 #region Properties 99 100 /// <summary> 101 /// 支持跟踪 102 /// </summary> 103 bool SupportCapture { get; } 104 105 /// <summary> 106 /// 支持部分退款 107 /// </summary> 108 bool SupportPartiallyRefund { get; } 109 110 /// <summary> 111 /// 支持退款 112 /// </summary> 113 bool SupportRefund { get; } 114 115 /// <summary> 116 /// 无效 117 /// </summary> 118 bool SupportVoid { get; } 119 120 /// <summary> 121 /// 定期付款方式 122 /// </summary> 123 RecurringPaymentType RecurringPaymentType { get; } 124 125 /// <summary> 126 /// 支付方式 127 /// </summary> 128 PaymentMethodType PaymentMethodType { get; } 129 130 /// <summary> 131 /// 是否显示插件的付款信息页面 132 /// </summary> 133 bool SkipPaymentInfo { get; } 134 135 /// <summary> 136 /// 在支付方式页面将显示的支付方式描述 137 /// </summary> 138 string PaymentMethodDescription { get; } 139 140 #endregion 141 } 142 }
重写安装、卸载处理方法,AliPayPaymentSettings类用于用于保存插件配置
最后看下安装、卸载处理流程
七.插件配置
接下来我们开发插件配置并支持多店铺设置,插件类中GetConfigurationRoute方法用于返回路由信息,我们定义一个路由到
AliPayController控制器的Configure方法下的路由,Configure用于处理配置。
Models文件夹中ConfigurationModel.cs类为模型类,用于沟通控制器与视图之间的数据。这里不再具体讨论。
八.支付功能
付款的时候我们需要跳转到支付宝页面进行操作,因此AliPayPaymentProcessor类中
PaymentMethodType返回PaymentMethodType.Redirection告诉nop我们需要到第三方进行支付。
1 public PaymentMethodType PaymentMethodType 2 { 3 get 4 { 5 return PaymentMethodType.Redirection; 6 } 7 }
然后实现PostProcessPayment方法,用于处理支付宝付款。Alipay文件夹里是支付宝辅助类。
调用支付宝接口时需要设置notify_url地址用于接收付款通知,return_url地址用支付成功后支付宝跳转到该地址。
1 notify_url = _webHelper.GetStoreLocation(false) + "Plugins/AliPay/Notify", 2 return_url = _webHelper.GetStoreLocation(false) + "Plugins/AliPay/Return",
RouteProvider.cs类用于注册路由,在这里定义上边两个地址的路由。
Plugins/AliPay/Notify 会调用AliPayController控制器类Notify方法进行处理
Plugins/AliPay/Return 会调用AliPayController控制器类Return方法进行处理
AliPayController->Notify方法用于接收支付宝通知,当支付成功后会调用该方法,该方法中会处理订单支付状态,并在付款信息表中添加付款记录。
AliPayController->Return方法定义返回位置。
九.退款功能
支持全额退款,需要插件类SupportRefund属性返回true,支持部分退款需要SupportPartiallyRefund属性返回true。
退款需要实现Refund方法,当退款操作时nop会调用Refund方法,本插件中会新建退款信息保存在退款表中,然后重定向到支付宝进行有密退款。
支付宝接口也需要notify_url地址,用于退款成功后接受通知,我们在前边RouteProvider路由表中定义了退款通知路由,AliPayController控制器RefundNotify方法来处理。
RefundNotify方法会改变订单付款状态,修改订单退款金额,退款记录表中修改已退款状态等等业务逻辑。具体实现请看源代码。
十.服务类
IPaymentInfoService接口:用于对付款记录处理,如对付款表的增删改查等。
IRefundInfoService接口:用于对退款记录的处理,如对退款表的增删改查等操作。
nop使用IRepository<T>来进行数据处理的封装(Dao层),T为实体类
1 #region 属性 2 private readonly IRepository<RefundInfo> _refundInfoRepository; 3 #endregion 4 #region 构造 5 public RefundInfoService(IRepository<RefundInfo> refundInfoRepository) 6 { 7 this._refundInfoRepository = refundInfoRepository; 8 } 9 #endregion
接下来创建接口实现类,PaymentInfoService和RefundInfoService用于实现业务逻辑。
最后我们需要把接口和实现类注册到依赖注入容器中,在DependencyRegistrar文件中进行注册。
1 //注册支付记录服务 2 builder.RegisterType<PaymentInfoService>().As<IPaymentInfoService>().InstancePerLifetimeScope(); 3 //注册退款记录服务 4 builder.RegisterType<RefundInfoService>().As<IRefundInfoService>().InstancePerLifetimeScope();
十一.总结
- 本文通过完整的实例介绍了支付插件接口的扩展。
- 实现IPaymentMethod接口用于支付插件接口扩展。
- 通过AliPayObjectContext数据库上下文扩展来对数据库表操作。
- 理解支付宝即时到账支付与退款流程。
- 路由注册、依赖注入注册。
注意:调试时项目一定要部署在公网中,这样支付宝才能正确的调用通知地址,否则插件就会失效。
文中有不正确的观点请指正,如果您觉得本文对您有帮助,请转载支持
本文地址:http://www.cnblogs.com/yaoshangjin/p/7290003.html
本文为大波浪原创、转载请注明出处。