支付 (Finance.Payment)
支付插件提供了通过各种收款接口(支付宝,财付通等)收取钱款的功能。
支付插件实现了交易(订单交易,充值交易等)和收款接口的分离,收款接口只需要实现统一的接口就能支持各种交易。
支付接口和交易的数据结构

在后台管理支付接口
后台可以添加删除支付接口,并且可以设置每个支付接口的参数和可以使用的交易。

在后台管理支付交易
后台可以查看发起的交易记录,每笔交易都有对应的详细日志。

添加新的收款接口
添加收款接口需要继承IPaymentApiHandler。
以下是收款接口的示例代码,注意每种收款接口的处理流程都有不同,需要根据实际情况编写。
以支付宝为例
- 接口数据应该保存商家邮箱和密钥
 - 获取支付Html时应该返回跳转到支付宝页面的html
 - 调用发货接口时应该调用支付宝提供的发货api
 - 同时还需要提供接收支付宝同步和异步通知的控制器
- 收到交易开始,付款成功等通知后,应该调用交易管理器通知指定的交易处理
 
 
/// <summary>
/// 测试接口的处理器
/// </summary>
[ExportMany]
public class TestApiHandler : IPaymentApiHandler {
    /// <summary>
    /// 接口类型
    /// </summary>
    public string Type { get { return "TestApi"; } }
    /// <summary>
    /// 编辑中的接口数据
    /// </summary>
    protected ApiData ApiDataEditing = new ApiData();
    /// <summary>
    /// 后台编辑表单创建后的处理
    /// </summary>
    public void OnFormCreated(PaymentApiEditForm form) {
        form.AddFieldsFrom(ApiDataEditing);
    }
    /// <summary>
    /// 后台编辑表单绑定时的处理
    /// </summary>
    public void OnFormBind(PaymentApiEditForm form, PaymentApi bindFrom) {
        var apiData = bindFrom.ExtraData.GetOrDefault<ApiData>("ApiData") ?? new ApiData();
        ApiDataEditing.PaymentPassword = apiData.PaymentPassword;
    }
    /// <summary>
    /// 后台编辑表单保存时的处理
    /// </summary>
    public void OnFormSubmit(PaymentApiEditForm form, PaymentApi saveTo) {
        saveTo.ExtraData["ApiData"] = ApiDataEditing;
    }
    /// <summary>
    /// 计算支付手续费
    /// </summary>
    public void CalculatePaymentFee(PaymentApi api, decimal amount, ref decimal paymentFee) {
        paymentFee = 0;
    }
    /// <summary>
    /// 获取支付Html
    /// </summary>
    public void GetPaymentHtml(PaymentTransaction transaction, ref HtmlString html) {
        var templateManager = Application.Ioc.Resolve<TemplateManager>();
        var form = new TestApiPayForm(transaction);
        form.Bind();
        html = new HtmlString(templateManager.RenderTemplate("finance.payment/test_api_pay.html", new { form }));
    }
    /// <summary>
    /// 调用发货接口
    /// 发货后自动确认收货
    /// </summary>
    public void SendGoods(
        PaymentTransaction transaction, string logisticsName, string invoiceNo) {
        var logManager = Application.Ioc.Resolve<LogManager>();
        logManager.LogTransaction(string.Format(
            "PaymentApi send goods: transaction {0} logisticsName {1} invoiceNo {2}",
            transaction.Serial, logisticsName, invoiceNo));
        var transactionManager = Application.Ioc.Resolve<PaymentTransactionManager>();
        transactionManager.Process(transaction.Id, null, PaymentTransactionState.Success);
    }
    /// <summary>
    /// 接口数据
    /// </summary>
    public class ApiData {
        /// <summary>
        /// 支付密码
        /// </summary>
        [Required]
        [StringLength(100, MinimumLength = 5)]
        [PasswordField("PaymentPassword", "Password required to pay transactions")]
        public string PaymentPassword { get; set; }
        /// <summary>
        /// 检查支付密码是否和设置的密码一致
        /// </summary>
        public void CheckPaymentPassword(string paymentPassword) {
            if (string.IsNullOrEmpty(PaymentPassword) || paymentPassword != PaymentPassword) {
                throw new ForbiddenException(new T("Incorrect payment password"));
            }
        }
    }
}
添加新的交易类型
添加交易类型需要继承IPaymentTransactionHandler。
以下是测试交易的示例代码,注意需要根据每种交易的实际流程进行编写。
以订单交易为例
- 交易创建后: 不需要做特殊的处理,只需要记录到日志
 - 等待付款时: 同上(在这个状态后订单金额应该不允许修改,否则有可能导致支付金额对不上)
 - 担保交易付款后: 修改订单状态为等待发货,并在发货时调用支付接口的发货接口通知
 - 交易成功时: 
- 如果之前的交易状态是担保交易付款后,代表已确认收货,这时应该设置订单状态为交易成功
 - 如果之前的交易状态时交易创建后,代表已付款,这时应该设置订单状态为等待发货
 
 - 交易终止时: 这时应该在订单上显示交易已终止提醒后台管理员和用户
 - 获取显示交易结果的Html: 显示当前的交易状态,并在一定时间后跳转到订单详情页
 
/// <summary>
/// 测试交易的处理器
/// </summary>
[ExportMany]
public class TestTransactionHandler : IPaymentTransactionHandler {
    /// <summary>
    /// 交易类型
    /// </summary>
    public string Type { get { return "TestTransaction"; } }
    /// <summary>
    /// 交易创建后
    /// </summary>
    public void OnCreated(PaymentTransaction transaction) {
        var logManager = Application.Ioc.Resolve<LogManager>();
        logManager.LogTransaction(string.Format("TestTransaction Created: {0}", transaction.Serial));
    }
    /// <summary>
    /// 等待付款时
    /// </summary>
    public void OnWaitingPaying(
        PaymentTransaction transaction, PaymentTransactionState previousState) {
        var logManager = Application.Ioc.Resolve<LogManager>();
        logManager.LogTransaction(string.Format("TestTransaction Waiting Paying: {0}", transaction.Serial));
    }
    /// <summary>
    /// 担保交易付款后
    /// 付款后自动发货
    /// </summary>
    public void OnSecuredPaid(PaymentTransaction transaction,
        PaymentTransactionState previousState, IList<AutoSendGoodsParameters> parameters) {
        var logManager = Application.Ioc.Resolve<LogManager>();
        logManager.LogTransaction(string.Format("TestTransaction Secured Paid: {0}", transaction.Serial));
        parameters.Add(new AutoSendGoodsParameters() { LogisticsName = "TestLogistics", InvoiceNo = "00000000" });
    }
    /// <summary>
    /// 交易成功时
    /// </summary>
    public void OnSuccess(
        PaymentTransaction transaction, PaymentTransactionState previousState) {
        var logManager = Application.Ioc.Resolve<LogManager>();
        logManager.LogTransaction(string.Format("TestTransaction Success: {0}", transaction.Serial));
    }
    /// <summary>
    /// 交易终止时
    /// </summary>
    public void OnAbort(
        PaymentTransaction transaction, PaymentTransactionState previousState) {
        var logManager = Application.Ioc.Resolve<LogManager>();
        logManager.LogTransaction(string.Format("TestTransaction Aborted: {0}", transaction.Serial));
    }
    /// <summary>
    /// 获取显示交易结果的Html
    /// </summary>
    public void GetResultHtml(PaymentTransaction transaction, IList<HtmlString> html) {
        var templateManager = Application.Ioc.Resolve<TemplateManager>();
        var args = new { serial = transaction.Serial, state = transaction.State.GetDescription() };
        html.Add(new HtmlString(
            templateManager.RenderTemplate("finance.payment/test_transaction_result.html", args)));
    }
}
创建交易
使用交易管理器可以创建交易。
创建交易前应该调用支付接口管理器计算好交易手续费(但一般都不会从付款人收取手续费)。
var transactionManager = Application.Ioc.Resolve<PaymentTransactionManager>();
var apiManager = Application.Ioc.Resolve<PaymentApiManager>();
var paymentFee = apiManager.CalculatePaymentFee(api.Id, Amount);
var transaction = transactionManager.CreateTransaction(
    "TestTransaction", api.Id, Amount, paymentFee,
    Currency, payerId, payeeId, payerId, Description);
完整交易流程(即时到账)
- 创建交易
- 调用
PaymentTransactionManager.CreateTransaction 
 - 调用
 - 跳转到支付页面
- 调用
PaymentTransactionManager.GetPaymentUrl - 支付接口可以跳转到支付平台上,也可以提供一个表单在网站内部处理支付
 - 支付接口应该设置一个返回页面
 
 - 调用
 - 用户支付完成
- 首先回到支付接口的返回页面
 - 支付接口通知支付交易处理支付完成
 - 处理成功时,跳转到显示支付结果的页面
- 调用
PaymentTransactionManager.GetResultUrl 
 - 调用
 
 
完整交易流程(担保交易)
- 创建交易
- 调用
PaymentTransactionManager.CreateTransaction 
 - 调用
 - 跳转到支付页面
- 调用
PaymentTransactionManager.GetPaymentUrl - 支付接口可以跳转到支付平台上,也可以提供一个表单在网站内部处理支付
 - 支付接口应该设置一个返回页面
 
 - 调用
 - 用户使用担保交易支付完成
- 首先回到支付接口的返回页面
 - 支付接口通知支付交易处理担保交易已付款
 - 处理成功时,跳转到显示支付结果的页面
- 调用
PaymentTransactionManager.GetResultUrl 
 - 调用
 
 - 等待后台发货
- 后台发货后,应该调用支付接口的发货api
- 调用
PaymentTransactionRepository.SendGoods 
 - 调用
 - 通知用户确认收货后,到支付平台上确认收货
 
 - 后台发货后,应该调用支付接口的发货api
 - 用户到支付平台上确认收货
- 支付平台给支付接口发出通知
 - 支付接口把交易状态设为交易成功
- 调用
PaymentTransactionRepository.Process 
 - 调用