聚合支付接口设计

前段时间对接了微信支付,于是乎,从网上找了一下别人写过的一顿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 ,

原文地址:https://www.cnblogs.com/coloz/p/13475341.html