在线支付子模块的设计与实现

时间:2024-04-16 11:37:53

        一个在线交易系统需要各种支付方式方便客户付款,这些支付方式按场景可分为在线支付和线下现付。线下现付常见的有货到付款、邮局汇款、银行电汇等非实时方式,在线支付主要包括网银直连(如工行、农行等)和第三方支付平台(如支付宝、微支付、快钱、银联等)。当然完善的支付方式还可以包含优惠券、积分兑换等功能,本文仅简述用于与银行/第三方支付平台对接的在线支付功能集成(从订单创建成功后开始支付到支付成功后反馈订单状态这个过程),不讨论自身作为第三方支付平台的设计与实现。

blob.png

图1:总体结构

        不同类型的订单处理方式不一样,每种类型的订单会涉及哪些行为呢?(1)获取应付款信息,(2)获取应退款信息,(3)付款成功后反馈订单状态,(4)退款成功后反馈订单状态。因此每种类型的订单处理逻辑可以抽象成一个类,上述4种行为即类的方法,每个方法返回必需的参数,基于此共同点使每种类型订单的处理类实现共同的接口。

using Payment.Model;

namespace Payment.BLL
{
    public interface ITrade
    {
        /// <summary>
        /// 获取订单应付款信息
        /// </summary>
        /// <param name="paymentWay">付款方式</param>        
        /// <param name="orderNo">订单号</param>
        /// <returns>订单支付信息</returns>
        GetOrderPayInfoResult GetOrderPayInfo(string paymentWay, string orderNo);

        /// <summary>
        /// 获取订单应退款信息
        /// </summary>
        /// <param name="paymentWay">付款方式</param>        
        /// <param name="orderNo">订单号</param>
        /// <returns>订单退款信息</returns>
        GetOrderRefundInfoResult GetOrderRefundInfo(string paymentWay, string orderNo);

        /// <summary>
        /// 付款成功后反馈订单状态
        /// </summary>
        /// <param name="paymentWay">付款方式</param>
        /// <param name="orderNo">订单号</param>
        /// <returns>支付结果</returns>
        PayOrderResult PayOrder(string paymentWay, string orderNo);

        /// <summary>
        /// 退款成功后反馈订单状态
        /// </summary>
        /// <param name="paymentWay">付款方式</param>
        /// <param name="orderNo">订单号</param>
        /// <returns>退款结果</returns>
        RefundOrderResult RefundOrder(string paymentWay, string orderNo);
    }
}

using System;

namespace Payment.Model
{
    /// <summary>
    /// 共同参数基类
    /// </summary>
    public class BaseResult
    {
        /// <summary>
        /// 是否成功
        /// </summary>
        public bool IsSuccess { get; set; }

        /// <summary>
        /// 操作标记
        /// </summary>
        public string Tag { get; set; }

        /// <summary>
        /// 提示信息
        /// </summary>
        public string Info { get; set; }
    }
    
    public class GetOrderPayInfoResult : BaseResult
    {    
        public string OrderType { get; set; }    
        
        public string OrderNo { get; set; }

        public string OrderTitle { get; set; }

        public string OrderMemo { get; set; }

        public decimal TotalFee { get; set; }

        /// <summary>
        /// 商品的展示网页URl
        /// </summary>
        public string ShowUrl { get; set; }     
    }    
    
    public class GetOrderRefundInfoResult : BaseResult
    {
        public string OrderType { get; set; }      
        
        public string OrderNo { get; set; }

        public string OrderTitle { get; set; }

        public string OrderMemo { get; set; }

        public decimal TotalFee { get; set; }   
    }    
    
    public class PayOrderResult : BaseResult
    {
        /// <summary>
        /// 付款成功返回的网页URL
        /// </summary>    
        public string ReturnUrl { get; set; }
    }    
    
    public class RefundOrderResult : BaseResult
    {

    }    
}  

        各银行或支付平台的支付接口虽然格式不一,但其基本原理是相同的。要集成某银行/支付平台的支付接口,至少需要三个基本功能:付款、退款、查询。客户下单后需要通过付款接口支付真实资金,当客户需要退货时系统调用退款接口自动退款,当系统需要知道某订单的真实资金是否到账则需要调用查询接口获取。因此每种支付方式可抽象成一个类,包含三个方法,每个方法返回必要的参数。

using Payment.Model;

namespace Payment.BLL
{
    public interface IPay
    {
        /// <summary>
        /// 返回付款跳转信息
        /// </summary>
        /// <returns>付款跳转信息</returns>
        PayResult Pay(GetOrderPayInfo info);

        /// <summary>
        /// 付款通知
        /// </summary>
        /// <returns></returns>
        PayNotifyResult PayNotify(HttpContext context);

        /// <summary>        
        /// 退款
        /// </summary>
        /// <returns></returns>
        RefundResult Refund(GetOrderRefundInfoResult info);
        
        /// <summary>
        /// 退款通知
        /// </summary>
        /// <returns></returns>
        RefundNotifyResult RefundNotify(HttpContext context);        

        /// <summary>
        /// 查询
        /// </summary>
        /// <returns></returns>
        QueryResult Qurey(GetOrderPayInfo info);
    }
}

using System;

namespace Payment.Model
{
    public class PayResult : BaseResult
    {        
        public string PayHtml { get; set; }    
        
        public string PayUrl { get; set; }   
    }    
    
    public class PayNotifyResult : BaseResult
    {
        public string OrderType { get; set; }      
        
        public string OrderNo { get; set; }

        public decimal TotalFee { get; set; }   
        
        ……
    }    
    
    public class RefundResult : BaseResult
    {
        /// <summary>
        /// 退款金额
        /// </summary>    
        public decimal RefundFee { get; set; }
        
        ……
    }    
    
    public class RefundNotifyResult : BaseResult
    {
        ……
    }    
    
    public class QueryResult : BaseResult
    {
        public string OrderType { get; set; }      
        
        public string OrderNo { get; set; }
        
        public string OrderStatus { get; set; }        

        public decimal TotalFee { get; set; }   
        
        ……
    }     
}

blob.png

图2:付款流程图

blob.png

图3:退款流程图

blob.png

图4:查询流程图

        付款、退款、查询这三种操作都是传入支付方式、订单类型和订单编号三个参数,采用Ioc控制反转根据支付方式调用响应银行/支付平台的类、根据订单类型调用相应类型订单处理类。以此实现易扩展、可复用。

        在线支付子模块的实现还需要注意其它几个问题:

(1)ITrade.PayOrder和ITrade.RefundOrder中的具体订单处理逻辑不能耗时太长,因为银行/支付平台异步通知时需要根据返回内容判断是否通知成功,若确实耗时过长,除了优化外,可以使用Thread、ThreadPool、Task等异步执行,或者使用消息队列转入后台处理;

(2)高并发时ITrade.PayOrder和ITrade.RefundOrder发生死锁的概率可能会较高,应做好数据库优化或采用消息队列串行化;

(3)为防止同一订单被重复处理,可以采用对每个订单加锁机制(lock或其它方式),在具体逻辑中也应判断是否重复处理;

(4)可能因为网络问题、服务器问题等造成异步通知无法收到,可部署异步服务通过银行/支付平台查询接口定时获取状态并处理;

(5)当ITrade.PayOrder和ITrade.RefundOrder发生异常时,除银行/支付平台会多次异步通知外,系统应能自动重试。