nopCommerce 3.9 大波浪系列 之 可退款的支付宝插件(下)

一.回顾

支付宝插件源码下载地址:点击下载      

    上篇介绍了使用支付宝插件进行支付,全额退款,部分退款还有插件的多店铺配置,本文介绍下如何实现的。

二.前期准备

    插件主要有3个功能:

  1. 多店铺插件配置 
  2. 支付功能
  3. 退款功能

   数据库支持:

  1. 新增dbl_PaymentInfo表保存支付记录。
  2. 新增dbl_RefundInfo表保存退款记录。

   其他准备:

  1. 支付宝即时到账PID和MD5秘钥Key,可通过支付宝开放平台获取。

三.流程规划 

  1. 插件安装卸载流程

FX]3{CKOJX5`(@G}_MUO6(D

   2.  支付流程

98UDRBDZ2Q_[`8O@%ZIZU_M

   3.  退款流程

{EM~~HSKQWNTTT$438(CJJ5

四.创建项目

    1.  新建类库项目,名称为 DaBoLang.Nop.Plugin.Payments.AliPay,位置放在Plugins下

image_thumb2

    2.  点击项目快捷键Alt+Enter进入项目属性,

         设置输出路径为..\..\Presentation\Nop.Web\Plugins\DaBoLang.Payments.AliPay\

image_thumb4

    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

image_thumb6

      最后添加 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. 创建项目目录,如果你已经下载了插件项目参考项目目录如下:

image_thumb[1]

  • Alipay  支付宝即时到账相关
  • Controllers 控制器文件夹
  • Data    数据库相关
  • Domain 实体类
  • Models  模型
  • Services  服务接口
  • Views  视图
  • AliPayPaymentProcessor.cs   支付插件实现类
  • AliPayPaymentSettings.cs     支付宝即时到账配置类
  • DependencyRegistrar.cs     依赖注入扩展类
  • Description.txt   插件描述文件
  • RouteProvider.cs    路由注册类

五.创建数据库

     项目我们已经创建好了。记下来我们创建付款记录表和退款记录表用于保存相关记录。nop使用的是ORM框架为Entity Framework框架,我们使用Code First模式来创建。

image

    1.  在Domain文件夹创建imagePaymentInfo类用于保存付款信息,RefundInfo类用于保存退款信息。

nop中的实体类需要继承 Nop.Core.BaseEntity 抽象方法。

  PaymentInfo
  RefundInfo

    2.  Data文件夹中创建实体与数据库的映射关系配置

image

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         }
复制代码

完整的代码如下

  AliPayObjectContext

    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类用于用于保存插件配置

  插件安装/卸载
  AliPayPaymentSettings

最后看下安装、卸载处理流程

FX]3{CKOJX5`(@G}_MUO6(D

七.插件配置

     接下来我们开发插件配置并支持多店铺设置,插件类中GetConfigurationRoute方法用于返回路由信息,我们定义一个路由到

AliPayController控制器的Configure方法下的路由,Configure用于处理配置。

     Models文件夹中ConfigurationModel.cs类为模型类,用于沟通控制器与视图之间的数据。这里不再具体讨论。

八.支付功能

    98UDRBDZ2Q_[`8O@%ZIZU_M

     付款的时候我们需要跳转到支付宝页面进行操作,因此AliPayPaymentProcessor类中

     PaymentMethodType返回PaymentMethodType.Redirection告诉nop我们需要到第三方进行支付。    

复制代码
  1  public PaymentMethodType PaymentMethodType
  2         {
  3             get
  4             {
  5                 return PaymentMethodType.Redirection;
  6             }
  7         }
复制代码

     然后实现PostProcessPayment方法,用于处理支付宝付款。Alipay文件夹里是支付宝辅助类。

  PostProcessPayment

     调用支付宝接口时需要设置notify_url地址用于接收付款通知,return_url地址用支付成功后支付宝跳转到该地址。

  1 notify_url = _webHelper.GetStoreLocation(false) + "Plugins/AliPay/Notify",
  2 return_url = _webHelper.GetStoreLocation(false) + "Plugins/AliPay/Return",

     RouteProvider.cs类用于注册路由,在这里定义上边两个地址的路由。

  RouteProvider

     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方法会改变订单付款状态,修改订单退款金额,退款记录表中修改已退款状态等等业务逻辑。具体实现请看源代码。

十.服务类

     Services文件夹用于保存服务类image

     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

本文为大波浪原创、转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值