当前位置:首页 > 技术积累 > 正文内容

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

竹林之风7年前 (2016-04-01)技术积累2104

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

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发生异常时,除银行/支付平台会多次异步通知外,系统应能自动重试。

扫描二维码推送至手机访问。

版权声明:本文由久爱编程网发布,如需转载请注明出处。

本文链接:http://www.9icode.com/index.php/post/15.html

标签: C#API.NET
分享给朋友:

相关文章

ASP.NET实现HTTP长轮询(一)——WebForm

ASP.NET实现HTTP长轮询(一)——WebForm

        本文主要描述如何在ASP.NET WebForm中实现长轮询:(1)在ASP.NET WebForm的aspx文件中设置如下指令即可实现异步的ASP.NET WebForm:<%...

IIS中使用PHP-CGI报500错误问题可能原因

IIS中使用PHP-CGI报500错误问题可能原因

        在IIS中使用CGI方式运行PHP网站突然报500服务器错误,需要判断当前系统的软件环境有何变化去一一排查,通常可能的原因有:(1)PHP程序因...

ASP.NET中报“无法在已发送HTTP标头之后进行重定向”异常问题解决

ASP.NET中报“无法在已发送HTTP标头之后进行重定向”异常问题解决

        ASP.NET中报“无法在已发送HTTP标头之后进行重定向”异常时,其中一个原因是:在已经重定向后又重定向。在ASP.NET中实现重定向有以下几...

ASP.NET中报“无法在已发送HTTP标头之后设置状态”异常问题解决

ASP.NET中报“无法在已发送HTTP标头之后设置状态”异常问题解决

        ASP.NET中报“无法在已发送HTTP标头之后设置状态”异常,是因为设置Response的StatusCode之前,程序已设置响应标头。可从以...

ASP.NET网站自定义错误处理及其它安全相关

ASP.NET网站自定义错误处理及其它安全相关

        ASP.NET网站在运行过程中总是有可能报错,例如404等HTTP错误、500等程序异常。在IIS托管并报错的情况下,网站的默认行为依次是:(1).NET运行时接收并处理的部分显示.N...

从被扫描记录看网站安全应该注意的一些细节

从被扫描记录看网站安全应该注意的一些细节

1、尽可能地隐藏服务器真实IP,减少攻击目标。例如使用负载均衡、网关等作为门户,由负载均衡或网关转发到后端服务器。2、服务器必须开放的服务(除HTTP、HTTPS等为公众开放的服务外),尽可能改为其它较大的不常用的端口号,这样攻击者需要扫描...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。