• 聚合支付接口设计


    前段时间对接了微信支付,于是乎,从网上找了一下别人写过的一顿copy后,修修改改终于实现完成了。

    本以为万事大吉, 但是项目经理review代码时候,发现我写的支付功能和系统业务功能高度耦合, 搅和在一起结果就扣了我一部分绩效(mmp…) 。

    为了避免以后扣绩效,所以决定研究一下,怎么设计支付接口比较合理。末尾附上 git传送门代码

    1. if 编码方式

    支付渠道暂时虽然只有微信,但是保不齐后面再加支付宝,银联啥的。到时候,代码就会像这样

    if (payType.equals ("微信")) {
    //dosomething
    }else if (payType.equals ("支付宝")) {
    //dosomething
    }else if(payType.equals ("银联")) {
    //dosomething
    }

    每多一个支付渠道,改动的地方包括:支付接口、支付配置、退款、统计业务。

    2.聚合支付

    万能的百度上搜索了一下对接多个系统的方案(参考末尾的链接),其中聚合支付的方案比较合理。虽然可能用不到其中一部分的功能,比如结算功能。

    1) 什么是聚合支付呢?

    说白了就是一个项目接入了多个支付渠道,而且能够使用任意一个渠道进行支付、退款等操作,而且任何渠道之间没有任何关系,彼此不会互相干扰。

    2) 简单梳理一下聚合支付的业务

      •需要对接多个支付渠道

      • 所有的支付能够兼容任意渠道

      • 所有的退款能够兼容任何渠道

      • 任何渠道都能需要独立进行配置

      • 任何渠道都有统计功能

      • 渠道之间能够无缝进行切换(比如某个渠道奔溃了,能够切换到其他渠道)

    如果想满足上面的功能,又不影响原有的业务的情况下,就需要将原有的支付模块独立抽离开来,单独作为一个服务,也就是聚合支付,凡是项目里面的任何支付、退款、查询、统计等都要通过聚合支付来处理。

    3) 如何设计

    设计模式是面试时候经常问的,那么,大胆地使用合理的设计模式对功能进行设计吧!

    工厂模式: 每个支付渠道可以看成一个工厂

    适配器模式: 不同的支付渠道使用的API,参数或者返回结果都可能不一样

    策略模式: 根据支付类型创建对应的支付通道

    3.工厂模式

    1) 创建一个支付的统一接口 , 这里列举几个接口

    /**
     * 支付接口, 所有支付类的接口,系统所有支付功能类都需要实现它;
     */
    public interface Pay {
    
        /**
         * 下单
         */
        DoOrderVo doOrder(DoOrderSo so);
    
        /**
         * 支付或者退款通知
         */
        PayNotifyVo payNotify(PayNotifySo so);
    
        /**
         * 查询退款
         */
        QueryRefundVo queryRefund(QueryRefundSo so);
    }

    2) 支付方式枚举类

    /**
     * 支付类型枚举类
     * <p>
     * 每增加一种支付渠道,需要同时在工厂类{@link PayFactory}里面配置
     * </p>
     */
    public enum PayWayEnum {
        /**
         * WX_APP
         */
        WX_APP("WX_APP"),
        /**
         * WX_NATIVE
         */
        WX_NATIVE("WX_NATIVE"),
        /**
         * ALI_APP
         */
        ALI_APP("ALI_APP"),
        /**
         * ALI_WEB
         */
        ALI_WEB("ALI_WEB");
    
    
        PayWayEnum(String key) {
        }
    
    }

    3) 实现类

    支付渠道有微信,支付宝,银联或者第三方支付等, 而微信又有Native支付,native支付,手机支付等方式....

    使用工厂方式可以根据支付方式枚举出来具体实现, 结构如下

     

                                                                               图一 支付接口

     4) 工厂获取实例对象 (又要用if...或者switch....,不过后面再优化....)

    @Service
    public class PayFactory {
    
        //根据类型获取结果处理的实现类
        public Pay getPayImpl(PayWayEnum payWayEnum) {
            Assert.notNull(payWayEnum, "付款渠道不能为空");
            if (payWayEnum.equals(PayWayEnum.WX_APP)) {
                return new WxPayAppImpl();
            } else if (payWayEnum.equals(PayWayEnum.WX_NATIVE)) {
                return new WxPayNativeImpl();
            } else if (payWayEnum.equals(PayWayEnum.ALI_APP)) {
                return new AliPayAppImpl();
            } else if (payWayEnum.equals(PayWayEnum.ALI_WEB)) {
                return new AliPayWebImpl();
            } else {
                return null;
            }
    
        }
    }

    4.适配器模式

    1) 多个实现Pay接口问题

    图一里面,通过观察发现:

    ①4个实现类都统一实现了pay的接口,如果再在pay接口中增加一个接口void doAction(),那么4个接口都得重新加上

    ②微信支付的API有些是通用的,比如统一下单,签名和验签,没必要在每个实现类里面都加上

    2) 接口适配器模式 (缺省适配器模式)

     适用场景: 当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求。

    微信支付抽象类:

    /**
     * 微信支付底层抽象类
     * <p>为对接第三方支付接口的支付抽象类,需要实现第三方支付接口的所有API交互,为支付功能类提供功能方法</p>
     * <p>每一种支付方法,都可以继承该抽象类,并拥有自己的独立的支付流程,</p>
     */
    public abstract class WxPay implements Pay {
    
        //=============================下面是支付的业务功能接口==================
    
        @Override
        public WxDoOrderVo doOrder(DoOrderSo so) {
            //子类实现
            return null;
        }
    
    
        @Override
        public PayNotifyVo payNotify(PayNotifySo so) {
            //子类实现
            return null;
        }
    
    
        @Override
        public QueryRefundVo queryRefund(QueryRefundSo so) {
            //子类实现
            return null;
        }
    
        //=============================下面是微信支付的基础API和相关方法==================
    
        /**
         * 统一下单接口
         *
         * @param so
         */
        public WxUnifiedOrderVo unifiedOrder(WxUnifiedOrderSo so) {
            System.out.println("WxPay->unifiedOrder :" + so.toString());
    
            /**
             * 这里调用微信支付API 发送下单请求,返回二维码链接等信息 ,
             */
            // 创建签名
            createSign(so.toString());
    
            //发送请求
            String resultXml = PaymenUtils.doPost("www.weixinpay/unifiedOrder", so.toString());
    
            //解析结果成实体,并返回
            WxUnifiedOrderVo wxUnifiedOrderVo = PaymenUtils.parseWxUnifiedOrderResult(resultXml);
    
            return wxUnifiedOrderVo;
        }
    
    
        /**
         * 生成签名
         */
        public void createSign(String params) {
            System.out.println("WxPay->createSign :" + params);
        }
    
        /**
         * 验证签名
         */
        public void checkSign(String params) {
            System.out.println("WxPay->checkSign :" + params);
        }
    
    }

    微信Native支付方式实现类 (假如我只想使用native下单,其他功能不需要,只需要重写一下doOrder方法,其他的不需要重写)

    @Service
    public class WxPayNativeImpl extends WxPay {
    
        @Override
        public WxDoOrderVo doOrder(DoOrderSo so) {
            System.out.println("------微信-APP方式-------");
    
            //调用统一下单逻辑
            WxUnifiedOrderSo unifiedOrderSo = new WxUnifiedOrderSo();
            WxUnifiedOrderVo unifiedOrderVo = super.unifiedOrder(unifiedOrderSo);
    
            WxNativeDoOrderVo wxNativeDoOrderVo = new WxNativeDoOrderVo();
            wxNativeDoOrderVo.setNativeFlag("------nativeflag------");
            wxNativeDoOrderVo.setWxUnifiedOrderVo(unifiedOrderVo);
    
            return wxNativeDoOrderVo;
        }
    
    }

    接口设计如下图

    5.策略模式

    1) 策略模式的定义

    策略模式是对算法的包装,把使用算法的责任和算法本身分隔开,委派给不同的对象管理。策略模式通常把一系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。

    2)  PayFactory 工厂优化

    前面工厂类,是根据if判断各种支付类型来new xxxPayImpl() 实例创建对象, 

    根据类型来实例化不同的对象,可以看做是多种实现策略,可以使用策略模式来优化一下;

    3) 建立支付方式->实现类的对应关系

       /**
         * 具体支付方式的配置
         * key 表示支付方式,
         * value 表示支付具体实现类,** 注意这里类名小写
         */
        public static final Map<PayWayEnum, String> PAY_MAP = new HashMap<>(8);
    static {
            PAY_MAP.put(PayWayEnum.WX_APP, "wxPayAppImpl");
            PAY_MAP.put(PayWayEnum.WX_NATIVE, "wxPayNativeImpl");
            PAY_MAP.put(PayWayEnum.ALI_APP, "aliPayAppImpl");
            PAY_MAP.put(PayWayEnum.ALI_WEB, "aliPayWebImpl");
           
        }

    4) Spring实例化bean(而不是手动new xxx)

     spring工具类

    /**
     * 直接通过Spring 上下文获取SpringBean,用于多线程环境
     */
    @Component
    public class SpringContextUtil implements ApplicationContextAware {
    
        // Spring应用上下文环境
        private static ApplicationContext applicationContext;
    
        /**
         * 实现ApplicationContextAware接口的回调方法。设置上下文环境
         */
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) {
            SpringContextUtil.applicationContext = applicationContext;
        }
    
    
        public static ApplicationContext getApplicationContext() {
            return applicationContext;
        }
    
        /**
         * 获取对象
         *
         * @param name
         * @return Object
         * @throws BeansException
         */
        public static Object getBean(String name) throws BeansException {
            return applicationContext.getBean(name);
        }
    
    
        public static void main(String[] args) {
            //具体使用:
            //   AliPay aliPayImpl =(AliPay) SpringContextUtil.getBean("aliPayImpl");
    
            //  aliPayImpl.pay();
        }
    }

    根据类型获取支付渠道的实现类

        //根据类型获取结果处理的实现类
        public Pay getPay(PayWayEnum payWayEnum) {
            Assert.notNull(payWayEnum, "付款渠道不能为空");
            return (Pay) SpringContextUtil.getBean(PAY_AFTER_MAP.get(payWayEnum));
        }

    6.Controller调用

    1) 请求接口

    @Autowired
    private PayFactory payFactory;

    @GetMapping("/hellopay/{typeEnum}") public String hello(@PathVariable("typeEnum") String typeEnumStr) { PayWayEnum typeEnum = PayWayEnum.valueOf(typeEnumStr);       //由工厂获取具体的pay实现类 Pay pay = payFactory.getPay(typeEnum); System.out.println("pay:" + pay); DoOrderSo doOrderSo = new DoOrderSo(); doOrderSo.setOrderNo("下单的单号OrderNo_00001"); doOrderSo.setTradeNo("下单的外部单号TradeNo_111111"); //获取处理结果,这里实际转换成了具体的结果实现类 DoOrderVo doOrderVo = pay.doOrder(doOrderSo); // 这里其实是具体的vo System.out.println("调用doOrder返回结果:doOrderVo :" + doOrderVo); return null; }

    说明: 这里的xxxSo 表示参数, xxxVo表示返回值, (可以自行定义), So和So之间存在父子关系,Vo和Vo之间也存在父子关系,

    这样设计主要是: 方便支付的API处理逻辑有一个统一的返回,然后再交给系统进行DB等业务处理~

    2) 测试请求

     http://localhost:8088/demo/testpay/hellopay/WX_NATIVE
     http://localhost:8088/demo/testpay/hellopay/ZFB_WEB

    请求会看到不同的效果,(ZFB_WEB的实现类需要按照上面那样写一点逻辑就可以看到效果)

    7.支付结果后处理

    DoOrderVo 是调用支付的API后,统一处理的实体,我们需要根据不同的类型,转发到不同的后处理service的具体业务实现类中,
    主要是在系统中记录一些DB信息和订单等信息,仿照上面的接口设计,
     //工厂获取支付后处理的实现类
            PayAfter payAfter = payFactory.getPayAfter(typeEnum);
            System.out.println("payAfter:" + payAfter);
            payAfter.doOrderAfter(doOrderVo);
    PayAfter是后处理统一接口,doOrderAfter是doOrder支付接口的后处理逻辑;

    8.调用效果

    请求: 

              http://localhost:8088/demo/testpay/hellopay/WX_NATIVE 

              http://localhost:8088/demo/testpay/hellopay/ALI_WEB

    结果: 

    git传送门代码:  https://github.com/ColoZhu/paydemo

     

    参考:  

     https://blog.csdn.net/think2017/article/details/79820786?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param ,

     https://www.cnblogs.com/lyc94620/p/13055116.html ,

  • 相关阅读:
    node.js(八 --- express)
    node.js(六 --- 文件系统模块fs)
    node.js( 五 --- 常用工具util)
    node.js(四 --- 全局对象)
    python 判断变量是否存在 防止报错
    python requests 的cookie 操作
    DDOS 攻击的防范
    python图片识别
    php常见问题-foreach和引用造成的问题。
    数据库数据类型选择
  • 原文地址:https://www.cnblogs.com/coloz/p/13475341.html
Copyright © 2020-2023  润新知