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

一.回顾

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

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

二.前期准备

插件主要有3个功能:

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

数据库支持:

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

其他准备:

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

三.流程规划

  1. 插件安装卸载流程

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 抽象方法。

  1 using Nop.Core;
  2 using Nop.Core.Domain.Customers;
  3 using Nop.Core.Domain.Orders;
  4 using System;
  5 using System.Collections.Generic;
  6 using System.Diagnostics;
  7 using System.Globalization;
  8 using System.Linq;
  9
 10 namespace DaBoLang.Nop.Plugin.Payments.AliPay.Domain
 11 {
 12     /// <summary>
 13     /// 命名空间:DaBoLang.Nop.Plugin.Payments.AliPay.Domain
 14     /// 名    称:PaymentInfo
 15     /// 功    能:实体类
 16     /// 详    细:付款记录
 17     /// 版    本:1.0.0.0
 18     /// 作    者:大波浪
 19     /// 联系方式:http://www.cnblogs.com/yaoshangjin
 20     /// 说    明:
 21     /// </summary>
 22     public partial class PaymentInfo : BaseEntity
 23     {
 24         #region Properties
 25         public Guid PaymentGuid { get; set; }
 26         /// <summary>
 27         /// 订单编号
 28         /// </summary>
 29         public int OrderId { get; set; }
 30         /// <summary>
 31         /// 插件SystemName
 32         /// </summary>
 33         public string Name { get; set; }
 34         /// <summary>
 35         /// 交易金额
 36         /// </summary>
 37         public decimal Total { get; set; }
 38         /// <summary>
 39         /// 订单编号外部交易号
 40         /// </summary>
 41         public string Out_Trade_No { get; set; }
 42         /// <summary>
 43         /// 说明
 44         /// </summary>
 45         public string Note { get; set; }
 46         /// <summary>
 47         /// 交易号,内部交易号,支付宝交易号或者微信交易号
 48         /// </summary>
 49         public string Trade_no { get; set; }
 50         /// <summary>
 51         /// 第三方交易状态
 52         /// </summary>
 53         public string Trade_status { get; set; }
 54         /// <summary>
 55         /// 收款单位email
 56         /// </summary>
 57         public string Seller_email { get; set; }
 58         /// <summary>
 59         /// 收款单位id
 60         /// </summary>
 61         public string Seller_id { get; set; }
 62         /// <summary>
 63         /// 付款账户id
 64         /// </summary>
 65         public string Buyer_id { get; set; }
 66         /// <summary>
 67         /// 付款账户email
 68         /// </summary>
 69         public string Buyer_email { get; set; }
 70        /// <summary>
 71        /// 内部订单创建时间
 72        /// </summary>
 73         public DateTime CreateDateUtc { get; set; }
 74
 75         #endregion
 76     }
 77 }
 78 

PaymentInfo

  1 using Nop.Core;
  2 using Nop.Core.Domain.Customers;
  3 using Nop.Core.Domain.Orders;
  4 using System;
  5 using System.Collections.Generic;
  6 using System.Diagnostics;
  7 using System.Globalization;
  8 using System.Linq;
  9
 10 namespace DaBoLang.Nop.Plugin.Payments.AliPay.Domain
 11 {
 12     /// <summary>
 13     /// 命名空间:DaBoLang.Nop.Plugin.Payments.AliPay.Domain
 14     /// 名    称:RefundInfo
 15     /// 功    能:实体类
 16     /// 详    细:退款记录
 17     /// 版    本:1.0.0.0
 18     /// 作    者:大波浪
 19     /// 联系方式:http://www.cnblogs.com/yaoshangjin
 20     /// 说    明:
 21     /// </summary>
 22     public partial class RefundInfo : BaseEntity
 23     {
 24
 25         #region Properties
 26         public int OrderId { get; set; }
 27         /// <summary>
 28         /// 退款状态
 29         /// </summary>
 30         public int RefundStatusId { get; set; }
 31         /// <summary>
 32         /// 退款金额
 33         /// </summary>
 34         public decimal AmountToRefund { get; set; }
 35         public string Seller_Email { get; set; }
 36         public string Seller_Id { get; set; }
 37         /// <summary>
 38         /// 交易号,内部交易号,支付宝交易号或者微信交易号
 39         /// </summary>
 40         public string Batch_no { get; set; }
 41         /// <summary>
 42         /// 订单标号外部交易号
 43         /// </summary>
 44         public string Out_Trade_No { get; set; }
 45         /// <summary>
 46         /// 创建时间
 47         /// </summary>
 48         public DateTime CreateOnUtc { get; set; }
 49         /// <summary>
 50         /// 退款成功时间
 51         /// </summary>
 52         public DateTime? RefundOnUtc { get; set; }
 53
 54         /// <summary>
 55         /// 回调ID
 56         /// </summary>
 57         public string Notify_Id { get; set; }
 58         /// <summary>
 59         /// 回调类型
 60         /// </summary>
 61         public string Notify_Type { get; set; }
 62
 63         public string Result_Details { get; set; }
 64         #endregion
 65
 66         /// <summary>
 67         /// 订单状态
 68         /// </summary>
 69         public RefundStatus RefundStatus
 70         {
 71             get
 72             {
 73                 return (RefundStatus)this.RefundStatusId;
 74             }
 75             set
 76             {
 77                 this.RefundStatusId = (int)value;
 78             }
 79         }
 80
 81     }
 82     public enum RefundStatus
 83     {
 84         /// <summary>
 85         /// 申请退款
 86         /// </summary>
 87         refunding = 10,
 88         /// <summary>
 89         /// 退款成功
 90         /// </summary>
 91         refund = 20,
 92         /// <summary>
 93         /// 取消退款
 94         /// </summary>
 95         cancel = 30,
 96         /// <summary>
 97         /// 退款过期
 98         /// </summary>
 99         overtime = 40,
100         /// <summary>
101         /// 退款失败
102         /// </summary>
103         error = 50,
104     }
105 }
106 

RefundInfo

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         }

完整的代码如下

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Data.Entity;
  4 using System.Data.Entity.Infrastructure;
  5 using Nop.Core;
  6 using Nop.Data;
  7 using DaBoLang.Nop.Plugin.Payments.AliPay.Domain;
  8
  9 namespace DaBoLang.Nop.Plugin.Payments.AliPay.Data
 10 {
 11     /// <summary>
 12     /// 命名空间:DaBoLang.Nop.Plugin.Payments.AliPay.Data
 13     /// 名    称:AliPayObjectContext
 14     /// 功    能:数据库
 15     /// 详    细:数据库上下文
 16     /// 版    本:1.0.0.0
 17     /// 文件名称:AliPayObjectContext.cs
 18     /// 作    者:大波浪
 19     /// 联系方式:http://www.cnblogs.com/yaoshangjin
 20     /// 说    明:
 21     /// </summary>
 22     public class AliPayObjectContext : DbContext, IDbContext
 23     {
 24         #region Ctor
 25
 26         public AliPayObjectContext(string nameOrConnectionString)
 27             : base(nameOrConnectionString)
 28         {
 29             //((IObjectContextAdapter) this).ObjectContext.ContextOptions.LazyLoadingEnabled = true;
 30         }
 31
 32         #endregion
 33
 34         #region Utilities
 35
 36         protected override void OnModelCreating(DbModelBuilder modelBuilder)
 37         {
 38             modelBuilder.Configurations.Add(new PaymentInfoMap());
 39             modelBuilder.Configurations.Add(new RefundInfoMap());
 40
 41             //disable EdmMetadata generation
 42             //modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
 43             base.OnModelCreating(modelBuilder);
 44         }
 45
 46         #endregion
 47
 48         #region Methods
 49
 50         public string CreateDatabaseScript()
 51         {
 52             return ((IObjectContextAdapter)this).ObjectContext.CreateDatabaseScript();
 53         }
 54
 55         public new IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity
 56         {
 57             return base.Set<TEntity>();
 58         }
 59
 60         /// <summary>
 61         /// 安装
 62         /// </summary>
 63         public void Install()
 64         {
 65             //create the table
 66             var dbScript = CreateDatabaseScript();
 67             Database.ExecuteSqlCommand(dbScript);
 68             SaveChanges();
 69         }
 70
 71         /// <summary>
 72         /// 卸载
 73         /// </summary>
 74         public void Uninstall()
 75         {
 76             //删除表
 77             var tableName = this.GetTableName<PaymentInfo>();
 78             this.DropPluginTable(tableName);
 79             tableName = this.GetTableName<RefundInfo>();
 80             this.DropPluginTable(tableName);
 81         }
 82
 83         /// <summary>
 84         /// Execute stores procedure and load a list of entities at the end
 85         /// </summary>
 86         /// <typeparam name="TEntity">Entity type</typeparam>
 87         /// <param name="commandText">Command text</param>
 88         /// <param name="parameters">Parameters</param>
 89         /// <returns>Entities</returns>
 90         public IList<TEntity> ExecuteStoredProcedureList<TEntity>(string commandText, params object[] parameters) where TEntity : BaseEntity, new()
 91         {
 92             throw new NotImplementedException();
 93         }
 94
 95         /// <summary>
 96         /// Creates a raw SQL query that will return elements of the given generic type.  The type can be any type that has properties that match the names of the columns returned from the query, or can be a simple primitive type. The type does not have to be an entity type. The results of this query are never tracked by the context even if the type of object returned is an entity type.
 97         /// </summary>
 98         /// <typeparam name="TElement">The type of object returned by the query.</typeparam>
 99         /// <param name="sql">The SQL query string.</param>
100         /// <param name="parameters">The parameters to apply to the SQL query string.</param>
101         /// <returns>Result</returns>
102         public IEnumerable<TElement> SqlQuery<TElement>(string sql, params object[] parameters)
103         {
104             throw new NotImplementedException();
105         }
106
107         /// <summary>
108         /// Executes the given DDL/DML command against the database.
109         /// </summary>
110         /// <param name="sql">The command string</param>
111         /// <param name="doNotEnsureTransaction">false - the transaction creation is not ensured; true - the transaction creation is ensured.</param>
112         /// <param name="timeout">Timeout value, in seconds. A null value indicates that the default value of the underlying provider will be used</param>
113         /// <param name="parameters">The parameters to apply to the command string.</param>
114         /// <returns>The result returned by the database after executing the command.</returns>
115         public int ExecuteSqlCommand(string sql, bool doNotEnsureTransaction = false, int? timeout = null, params object[] parameters)
116         {
117             throw new NotImplementedException();
118         }
119
120         /// <summary>
121         /// Detach an entity
122         /// </summary>
123         /// <param name="entity">Entity</param>
124         public void Detach(object entity)
125         {
126             if (entity == null)
127                 throw new ArgumentNullException("entity");
128
129             ((IObjectContextAdapter)this).ObjectContext.Detach(entity);
130         }
131
132         #endregion
133
134         #region Properties
135
136         /// <summary>
137         /// Gets or sets a value indicating whether proxy creation setting is enabled (used in EF)
138         /// </summary>
139         public virtual bool ProxyCreationEnabled
140         {
141             get
142             {
143                 return this.Configuration.ProxyCreationEnabled;
144             }
145             set
146             {
147                 this.Configuration.ProxyCreationEnabled = value;
148             }
149         }
150
151         /// <summary>
152         /// Gets or sets a value indicating whether auto detect changes setting is enabled (used in EF)
153         /// </summary>
154         public virtual bool AutoDetectChangesEnabled
155         {
156             get
157             {
158                 return this.Configuration.AutoDetectChangesEnabled;
159             }
160             set
161             {
162                 this.Configuration.AutoDetectChangesEnabled = value;
163             }
164         }
165
166         #endregion
167     }
168 }

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

  1 #region 插件安装/卸载
  2
  3         public override void Install()
  4         {
  5             //配置
  6             var settings = new AliPayPaymentSettings
  7             {
  8                 SellerEmail = "",
  9                 Key = "",
 10                 Partner = "",
 11                 AdditionalFee = 0,
 12             };
 13
 14             _settingService.SaveSetting(settings);
 15
 16             //安装数据表
 17             _objectContext.Install();
 18
 19             //本地化资源
 20             this.AddOrUpdatePluginLocaleResource("DaBoLang.Plugins.Payments.AliPay.RedirectionTip", "您将被重定向到支付宝网站完成订单.");
 21             this.AddOrUpdatePluginLocaleResource("DaBoLang.Plugins.Payments.AliPay.SellerEmail", "卖方邮箱");
 22             this.AddOrUpdatePluginLocaleResource("DaBoLang.Plugins.Payments.AliPay.SellerEmail.Hint", "支付宝卖方电子邮箱.");
 23             this.AddOrUpdatePluginLocaleResource("DaBoLang.Plugins.Payments.AliPay.Key", "Key");
 24             this.AddOrUpdatePluginLocaleResource("DaBoLang.Plugins.Payments.AliPay.Key.Hint", "输入 key.");
 25             this.AddOrUpdatePluginLocaleResource("DaBoLang.Plugins.Payments.AliPay.Partner", "Partner");
 26             this.AddOrUpdatePluginLocaleResource("DaBoLang.Plugins.Payments.AliPay.Partner.Hint", "输入 partner.");
 27             this.AddOrUpdatePluginLocaleResource("DaBoLang.Plugins.Payments.AliPay.AdditionalFee", "额外费用");
 28             this.AddOrUpdatePluginLocaleResource("DaBoLang.Plugins.Payments.AliPay.AdditionalFee.Hint", "客户选择此支付方式将付额外的费用.");
 29             this.AddOrUpdatePluginLocaleResource("DaBoLang.Plugins.Payments.AliPay.PaymentMethodDescription", "使用支付宝进行支付");
 30
 31             base.Install();
 32         }
 33
 34         public override void Uninstall()
 35         {
 36             //配置
 37             _settingService.DeleteSetting<AliPayPaymentSettings>();
 38
 39             //本地化资源
 40             this.DeletePluginLocaleResource("DaBoLang.Plugins.Payments.AliPay.SellerEmail.RedirectionTip");
 41             this.DeletePluginLocaleResource("DaBoLang.Plugins.Payments.AliPay.SellerEmail");
 42             this.DeletePluginLocaleResource("DaBoLang.Plugins.Payments.AliPay.SellerEmail.Hint");
 43             this.DeletePluginLocaleResource("DaBoLang.Plugins.Payments.AliPay.Key");
 44             this.DeletePluginLocaleResource("DaBoLang.Plugins.Payments.AliPay.Key.Hint");
 45             this.DeletePluginLocaleResource("DaBoLang.Plugins.Payments.AliPay.Partner");
 46             this.DeletePluginLocaleResource("DaBoLang.Plugins.Payments.AliPay.Partner.Hint");
 47             this.DeletePluginLocaleResource("DaBoLang.Plugins.Payments.AliPay.AdditionalFee");
 48             this.DeletePluginLocaleResource("DaBoLang.Plugins.Payments.AliPay.AdditionalFee.Hint");
 49             this.DeletePluginLocaleResource("DaBoLang.Plugins.Payments.AliPay.PaymentMethodDescription");
 50
 51             //卸载数据表
 52             _objectContext.Uninstall();
 53
 54             base.Uninstall();
 55         }
 56
 57         #endregion

插件安装/卸载

  1 using Nop.Core.Configuration;
  2
  3 namespace DaBoLang.Nop.Plugin.Payments.AliPay
  4 {
  5     /// <summary>
  6     /// 命名空间:DaBoLang.Nop.Plugin.Payments.AliPay
  7     /// 名    称:AliPayPaymentSettings
  8     /// 功    能:配置类
  9     /// 详    细:支付宝配置
 10     /// 版    本:1.0.0.0
 11     /// 文件名称:AliPayPaymentSettings.cs
 12     /// 作    者:大波浪
 13     /// 联系方式:http://www.cnblogs.com/yaoshangjin
 14     /// 说    明:
 15     /// </summary>
 16     public class AliPayPaymentSettings : ISettings
 17     {
 18         /// <summary>
 19         /// 卖家Email
 20         /// </summary>
 21         public string SellerEmail { get; set; }
 22         /// <summary>
 23         /// Key
 24         /// </summary>
 25         public string Key { get; set; }
 26         /// <summary>
 27         /// PID
 28         /// </summary>
 29         public string Partner { get; set; }
 30         /// <summary>
 31         /// 额外费用
 32         /// </summary>
 33         public decimal AdditionalFee { get; set; }
 34     }
 35 }
 36 

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文件夹里是支付宝辅助类。

  1  public void PostProcessPayment(PostProcessPaymentRequest postProcessPaymentRequest)
  2         {
  3             var partner = _aliPayPaymentSettings.Partner;
  4
  5             if (string.IsNullOrEmpty(partner))
  6                 throw new Exception("合作身份者ID 不能为空");
  7
  8             var key = _aliPayPaymentSettings.Key;
  9
 10             if (string.IsNullOrEmpty(key))
 11                 throw new Exception("MD5密钥不能为空");
 12
 13             var sellerEmail = _aliPayPaymentSettings.SellerEmail;
 14
 15             if (string.IsNullOrEmpty(sellerEmail))
 16                 throw new Exception("卖家Email 不能为空");
 17
 18             var customer = _workContext.CurrentCustomer;//当前用户
 19             string username = customer.Username;
 20
 21
 22             //商户订单号,商户网站订单系统中唯一订单号,必填
 23             string out_trade_no = postProcessPaymentRequest.Order.Id.ToString().Trim();//订单编号
 24
 25             //订单名称,必填
 26             string subject = _storeContext.CurrentStore.Name + ":订单" + out_trade_no;
 27
 28             //付款金额,必填
 29             string total_fee = postProcessPaymentRequest.Order.OrderTotal.ToString("0.00", CultureInfo.InvariantCulture);
 30
 31             //商品描述,可空
 32             string body = _storeContext.CurrentStore.Name + ":用户_" + username;
 33
 34             //支付配置信息
 35             var aliPayDirectConfig = new AlipayDirectConfig()
 36             {
 37                 key = _aliPayPaymentSettings.Key,
 38                 partner = _aliPayPaymentSettings.Partner,
 39                 seller_email = _aliPayPaymentSettings.SellerEmail,
 40                 notify_url = _webHelper.GetStoreLocation(false) + "Plugins/AliPay/Notify",
 41                 return_url = _webHelper.GetStoreLocation(false) + "Plugins/AliPay/Return",
 42                 sign_type = "MD5",
 43                 input_charset= "utf-8",
 44             };
 45             //把请求参数打包成数组
 46             SortedDictionary<string, string> sParaTemp = new SortedDictionary<string, string>();
 47             sParaTemp.Add("service", aliPayDirectConfig.service);
 48             sParaTemp.Add("partner", aliPayDirectConfig.partner);
 49             sParaTemp.Add("seller_email", aliPayDirectConfig.seller_email);
 50             sParaTemp.Add("payment_type", aliPayDirectConfig.payment_type);
 51             sParaTemp.Add("notify_url", aliPayDirectConfig.notify_url);
 52             sParaTemp.Add("return_url", aliPayDirectConfig.return_url);
 53             sParaTemp.Add("_input_charset", aliPayDirectConfig.input_charset);
 54             sParaTemp.Add("out_trade_no", out_trade_no);
 55             sParaTemp.Add("subject", subject);
 56             sParaTemp.Add("body", body);
 57             sParaTemp.Add("total_fee", total_fee);
 58             //创建支付宝请求
 59             var post = AlipaySubmit.BuildRequest(sParaTemp, aliPayDirectConfig, "POST");
 60             post.Post();
 61
 62         }

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类用于注册路由,在这里定义上边两个地址的路由。

  1 using System.Web.Mvc;
  2 using System.Web.Routing;
  3 using Nop.Web.Framework.Mvc.Routes;
  4
  5 namespace DaBoLang.Nop.Plugin.Payments.AliPay
  6 {
  7     /// <summary>
  8     /// 命名空间:DaBoLang.Nop.Plugin.Payments.AliPay
  9     /// 名    称:RouteProvider
 10     /// 功    能:路由
 11     /// 详    细:定义路由
 12     /// 版    本:1.0.0.0
 13     /// 文件名称:RouteProvider.cs
 14     /// 作    者:大波浪
 15     /// 联系方式:http://www.cnblogs.com/yaoshangjin
 16     /// 说    明:
 17     /// </summary>
 18     public partial class RouteProvider : IRouteProvider
 19     {
 20         #region Methods
 21
 22         public void RegisterRoutes(RouteCollection routes)
 23         {
 24             //支付通知路由
 25             routes.MapRoute("DaBoLang.Plugin.Payments.AliPay.Notify",
 26                  "Plugins/AliPay/Notify",
 27                  new { controller = "AliPay", action = "Notify" },
 28                  new[] { "DaBoLang.Nop.Plugin.Payments.AliPay.Controllers" }
 29             );
 30
 31             //支付页面跳转同步通知页面
 32             routes.MapRoute("DaBoLang.Plugin.Payments.AliPay.Return",
 33                  "Plugins/AliPay/Return",
 34                  new { controller = "AliPay", action = "Return" },
 35                  new[] { "DaBoLang.Nop.Plugin.Payments.AliPay.Controllers" }
 36             );
 37
 38             //退款通知路由
 39             routes.MapRoute("Plugin.Payments.AliPay.RefundNotify",
 40               "Plugins/AliPay/RefundNotify",
 41               new { controller = "AliPay", action = "RefundNotify" },
 42               new[] { "DaBoLang.Nop.Plugin.Payments.AliPay.Controllers" }
 43             );
 44         }
 45
 46         #endregion
 47
 48         #region Properties
 49
 50         public int Priority
 51         {
 52             get
 53             {
 54                 return 0;
 55             }
 56         }
 57
 58         #endregion
 59     }
 60 }
 61 

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文件夹用于保存服务类

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

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

时间: 2024-08-06 09:00:45

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

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

一.简介 nop通过插件机制可以支持更多的支付扩展,我们通过编写支持退款的支付宝插件来更好的理解支付插件的扩展. 先分享下支付宝插件源码点击下载,由于时间原因,本篇只介绍使用该插件,下一篇结合插件进行代码分析.       注意:测试时将项目部署在公网,这样才能接收来自支付宝的回调通知,否则无法接收支付宝回调会导致订单状态无法处理. 二.插件使用 1.将插件DaBoLang.Nop.Plugin.Payments.AliPay项目放置在"nopCommerce_3.90_Source\Plugi

nopCommerce 3.9 大波浪系列 之 微信公众平台登录插件

一.简介 nop支持第三方登录授权扩展,本篇通过编写微信公众平台登录插件进一步了解nop授权登录的开发过程. 微信公众平台.微信开放平台使用场景不一样,前者通过微信客户端进行开发如公众号,后者基于网站或第三方应用这里不多说,本插件是基于微信公众平台开发,因此测试需要使用到微信web开发者工具. 插件源码下载:点击下载 微信公众平台网站授权帮助地址:传送门 微信web开发者工具使用下载地址:传送门 微信公众平台接口测试账号申请地址:传送门 二.插件使用 1.将插件DaBoLang.Nop.Plug

nopCommerce 3.9 大波浪系列 之 使用Redis主从高可用缓存

一.概述 nop支持Redis作为缓存,Redis出众的性能在企业中得到了广泛的应用.Redis支持主从复制,HA,集群. 一般来说,只有一台Redis是不可行的,原因如下: 单台Redis服务器会发生单点故障,并且单服务器需要处理所有的请求会导致压力较大. 单台Redis服务器内存容量有限,不易扩展. 第一个问题可以通过Redis主从模式实现单节点的高可用(HA). 从节点(slave)是主节点(master)副本,当主节点(master)宕机后,Redis 哨兵(Sentinel)会自动将从

nopCommerce 3.9 大波浪系列 之 开发支持多店的插件

一.基础介绍 nop支持多店及多语言,本篇结合NivoSlider插件介绍下如何开发支持多商城的小部件. 主要接口如下: ISettingService 接口:设置接口,可实现多店配置. ILocalizationService 接口:本地化资源接口,配合语言,实现多语言的显示. 二.插件安装.卸载 我们在上一篇介绍了小部件如何使用(点击这里),这里介绍下如何安装.卸载插件. [后台管理][插件管理][本地插件]可以对插件进行安装.卸载 Nop.Admin.Controllers.PluginC

nopCommerce 3.9 大波浪系列 之 引擎 NopEngine

本章涉及到的内容如下 1.EngineContext初始化IEngine实例 2.Autofac依赖注入初始化 3.AutoMapper框架初始化 4.启动任务初始化 一.EngineContext初始化 nopCommerce应用启动时首先调用EngineContext.Initialize(false) 进行初始化引擎, 并对IEngine接口进行初始化.IEngine用于实现依赖注入和初始化工作.nop中使用Autofac进行依赖注入. 你会发现nop中很多如:EngineContext.

nopCommerce 3.9 大波浪系列 之 汉化-Roxy Fileman

官网:http://www.roxyfileman.com/ 中文包:zh.json 1.将zh.json包拷贝到Nop.Admin项目中"Content\Roxy_Fileman\lang\"下 2.conf.json修改"LANG":"auto" 1 { 2 "CreateDir":"创建", 3 "RenameDir":"重命名", 4 "Delete

nopCommerce 3.9 大波浪系列 之 global.asax

一.nop的global.asax文件 nop3.9基于ASP.NET MVC 5框架开发,而ASP.NET MVC中global.asax文件包含全局应用程序事件的事件处理程序,它响应应用程序级别和会话级别事件的代码. nop应用启动时调用 Application_Start 客户端请求时依次调用 Application_BeginRequest,Application_AuthenticateRequest,Application_EndRequest 请求未处理异常时调用 Applicat

nopCommerce 3.9 大波浪系列 之 路由扩展 [多语言Seo的实现]

一.nop种的路由注册 在Global.asax,Application_Start()方法中会进行路由注册,代码如下. 1 public static void RegisterRoutes(RouteCollection routes) 2 { 3 routes.IgnoreRoute("favicon.ico"); 4 routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 5 6 //register custom

nopCommerce 3.9 大波浪系列 之 汉化-中文语言

一.导入语言包 nop官网下载中文语言文件,下载后是一个xml文件. 打开xml文件我们可以看到LocaleResource节点Name属性为资源名称,Value节点为资源值. 后台[Confignuration][Languages]进入语言管理界面. 点击右上角Add new按钮按下图所示添加语言 点击[Save and Continue Edit]按钮保存并继续编辑. 点击[admin.configuration.languages.import ]按钮选择中文语言xml文件. 最后[ad