聚合支付接口設計


前段時間對接了微信支付,於是乎,從網上找了一下別人寫過的一頓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 ,

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM