支付模塊重構整理與總結

    由於之前支付模塊與訂單耦合,也就是說如果要支付,必須要走一個訂單流程,但是大家都知道:訂單流程是很複雜的,而且訂單只是支付的一種源頭,因此就對這塊代碼進行了重構,解耦。


    首先,我們系統支付的接口目前集成支付寶和財富通,而且整個支付過程涉及以下幾個環節:

  •  發請求,即向第三方支付接口發請求參數
  • 正常返回的接收以及處理
  • 對Notify請求的接收和處理。

  首先,我們來抽象一下發請求的部分:
  1. 封裝請求參數
    public class PayInfoWrapper {
    
        private List<String> bankList = new LinkedList<String>();
    
        private String defaultbank;
    
    
        private String callBackClass;
    
        /**
         * 交易總金額,以分爲單位
         */
        private long totalFee;
    
    
        /**
         * 交易流水號
         */
        private String tradeNo = TradeSequenceUtil.getTradeNo();
    
        /**
         *支付方式
         */
        private PayMethod payMethod;
    
    }

    此處省去了必要的getter 和 setter。支付包括的參數: 交易金額,交易流水號,還有你的支付方式(財富通,支付寶,銀行也隸屬於支付寶,因爲支付寶兼容銀行),還有本次交易成功後的回調處理類。

  2. 封裝發請求的類
   
  我們抽象了一個父類PayRequestHandler,也就是支付請求處理類,從類圖上可以看到,他有一個public方法,3個protected方法,很自然想到了是模板方式。讓我們來看一下forwaridToPay方法裏面的內容:
public abstract class PayRequestHandler {


    private static final String PAYMENT_PROPERTIES_LOCATION = "payment.properties";




    private static Properties payProp = null;
    /**
     * 默認的reutrn url
     */
    protected static final String DEFAULT_RETURN_URL_KEY = "return.url";


    /**
     *  默認的notify url
     */
    protected static final String DEFAULT_NOTIFY_URL_KEY = "notify.url";


    /**
     * 初始化加載return.url和notify.url
     */
    static {
        try {
            payProp = PropertiesUtil.loadProperties(PAYMENT_PROPERTIES_LOCATION);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }




    public final String forwardToPay(PayInfoWrapper payInfoWrapper) {
        Map<String, String> params = buildPayParam(payInfoWrapper);
        //返回處理的Url
        String returnUrl =  payProp.getProperty(DEFAULT_RETURN_URL_KEY);
        //notify處理的Url
        String notifyUrl = payProp.getProperty(DEFAULT_NOTIFY_URL_KEY);
        Assert.notNull(returnUrl,"必須要在payment.properties中配置return url");
        Assert.notNull(notifyUrl,"必須要在payment.properties中配置notify url");
        params.put("return_url",returnUrl);
        params.put("notify_url",notifyUrl);
        //簽名,並追加簽名參數
        doSign(params);
        return buildPaymentString(params);


    }


    protected String buildPaymentString(Map<String, String> params) {
        StringBuilder sbHtml = new StringBuilder();
        sbHtml.append("<form id=\"payForm\" name=\"payForm\" action=\"" + getPaymentURL() + "\" "
                + "method=\"get\">");
        for (Map.Entry<String, String> entry : params.entrySet()) {
            sbHtml.append("<input type=\"hidden\" name=\"" + entry.getKey() + "\" value=\"" + entry.getValue() + "\"/>");
        }
        sbHtml.append("<input type=\"submit\" value=\"" + "確認" + "\" style=\"display:none;\"></form>");
        sbHtml.append("<script>document.forms['payForm'].submit();</script>");
        return sbHtml.toString();
    }




    protected abstract Map<String, String> buildPayParam(PayInfoWrapper payInfoWrapper);


    protected abstract String getPaymentURL();




    protected abstract void doSign(Map<String, String> params);}
我們先是掉用buildPayParam,來獲取支付參數,這個方法是一個抽象方法,由子類各自實現,因爲每種支付方式都有各自不同的參數。 接着從配置文件中讀取returnUrl 和notifyUrl。 也就是我們一開始提及的2個處理返回結果的地址。
buildPaymentString,就是構建一個自定提交的表單,這纔是真正的發送支付請求的類。


接下的問題就是我們怎麼知道,調用那個實現類來發送支付請求列?於是就寫了一個管理類:
public class PaymentManager {

    private static Map<PayMethod,Class<? extends PayRequestHandler>> payServiceMap = new HashMap<PayMethod,Class<? extends PayRequestHandler>>();

    /**
     *將默認的方式初始化管理,並提供  addPayService()以方便運時添加
     */
    static{
        //支付寶,實現類
        addPayService(PayMethod.directPay, AliPayRequestHandler.class);
        //銀行,目前也是通過支付寶來實現
        addPayService(PayMethod.bankPay, AliPayRequestHandler.class);
         //財富通,實現類
        addPayService(PayMethod.Tenpay, TenPayRequestHandler.class);
    }

    /**
     * 得到支付實例
     * @param payMethod
     * @return
     */
    public static PayRequestHandler getPayRequestHandler(PayMethod payMethod){
        try {
            return payServiceMap.get(payMethod).newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 提供的對外接口,以方便運行時添加
     * @param payMethod
     * @param payService
     */
    public static void addPayService(PayMethod payMethod,Class<? extends PayRequestHandler> payService){
        payServiceMap.put(payMethod,payService);
    }

}



這樣在Controller裏面,你只需要封裝好你的PayInfoWrapper, 然後得到payRequestHandler,然後調用支付接口。如下代碼:
  @RequestMapping("/test")
    public String testPay(Model model){
        PayInfoWrapper wrapper = new PayInfoWrapper();
        wrapper.setTotalFee(1);
        wrapper.setCallBackClass(OrderPayCallback.class.getName());
        wrapper.setPayMethod(PayMethod.directPay);
        PayRequestHandler handler = PaymentManager.getPayRequestHandler(wrapper.getPayMethod());
        model.addAttribute("form", handler.forwardToPay(wrapper));
        return "payTo";
    }

那麼這整個發支付請求的過程,就完成了。



接下來,我們就看一下支付完成後,回調部分如何處理了,我們要達到用一個Controller就能處理所有的返回結果。

經常一系列的抽象,抽象出了幾個對象: 交易信息,返回接收類,返回後的業務處理類,業務處理後的返回結果。


請求的統一處理地方
public class PayResponseHandler {

    private static Logger logger = Logger.getLogger(PayResponseHandler.class);

    //交易信息
    private TradeInfo tradeInfo;
    //送過去的交易回調處理類
    private String callBackHandlerClass;

    private TradeInfoBuilder builder;
    //所有返回的參數,原樣封裝
    private Map<String,String> backParams;


    public PayResponseHandler(HttpServletRequest request) {
        String extra_common_param = request.getParameter("extra_common_param");//阿里的支付回傳參數
        String attach = request.getParameter("attach");//騰訊的回傳參數

        if (!StringUtils.isEmpty(extra_common_param)) {
            this.builder = new AliTradeInfoBuilder();
            this.callBackHandlerClass = extra_common_param.trim();
        }

        if (!StringUtils.isEmpty(attach)) {
            this.builder = new TenTradeInfoBuilder();
            this.callBackHandlerClass = attach.trim();
        }
        Assert.notNull(this.callBackHandlerClass,"沒有定義支付回調處理接口類");

        this.tradeInfo = this.builder.buildFromRequest(request);
        this.backParams = buildParam(request);

    }

    public CallBackResult handleCallback() {
        CallBackResult result ;
        try {
            Class<PayCallback> classz = (Class<PayCallback>) Class.forName(callBackHandlerClass);
            result = classz.newInstance().doAfterSuccess(this.tradeInfo,this.backParams);
            return result;
        } catch (ClassNotFoundException e) {
            logger.error("未定義的支付返回處理類:" + callBackHandlerClass, e);
            e.printStackTrace();
        } catch (Exception e) {
            logger.error("支付返回處理時發生了錯誤", e);
            e.printStackTrace();
        }
        result = new CallBackResult();
        result.setResult(false);
        return result;
    }



    private Map<String, String> buildParam(HttpServletRequest request) {
        //獲取GET過來反饋信息
        Map<String, String> params = new HashMap<String, String>();
        Map requestParams = request.getParameterMap();
        for (Object oName : requestParams.keySet()) {
            String name = (String) oName;
            String[] values = (String[]) requestParams.get(name);
            String valueStr = "";
            for (int i = 0; i < values.length; i++) {
                valueStr = (i == values.length - 1) ? valueStr + values[i]
                        : valueStr + values[i] + ",";
            }
            params.put(name, valueStr);
        }
        return params;
    }

}

TradeInfo,就是封裝的交易信息
TradeInfoBuilder,顧名思義就是構建TradeInfo的, 因爲每種支付方式的參數不同,所以構建方式必須不同。
public interface TradeInfoBuilder {


    public abstract TradeInfo buildFromRequest(HttpServletRequest request);
}

業務處理類的接口:
/**
 * 支付請求成功返回後的業務處理接口
 * User: amos.zhou
 * Date: 13-10-24
 * Time: 上午11:54
 */
public interface PayCallback {

    /**
     * 在支付請求成功返回後,處理自己的回調業務
     * @param tradeInfo    封裝好的交易信息
     * @param backParams   所有返回的參數的 Map,方便特殊的自定義處理
     * @return        CallBackResult,包括支付結果(true || false),以及需要返回給調用端(Controller,頁面)的參數,以及需要跳轉的頁面等。
     */
   public CallBackResult doAfterSuccess(TradeInfo tradeInfo, Map<String, String> backParams);
}

業務處理類的返回值(getter,setter省去):
public class CallBackResult {

    private boolean result;
    private Map<String, Object> data = new HashMap<String, Object>();
    private String successUrl;
    private String failureUrl;


    /**
     * 跳轉至下一個處理流程
     * @return
     */
    public String skipToNextProcess() {
        if(StringUtils.isEmpty(this.successUrl) || StringUtils.isEmpty(this.failureUrl)){
            throw new IllegalArgumentException("沒有設置待跳轉的Url,不能進行跳轉。請確保successUrl與failureUrl已被正確初始化");
        }
        if (success()) {
            return this.successUrl;
        }
        return this.failureUrl;
    }
}

那麼統一接收返回值的Controller就很明瞭:
public class PayCallBackController {


    @RequestMapping(value = "/return")
    @RenderHeaderFooter
    public String normalReturn(HttpServletRequest request, Model model) {
        PayResponseHandler handler = new PayResponseHandler(request);
        CallBackResult result = handler.handleCallback();
        model.addAllAttributes(result.getData());
        return result.skipToNextProcess();
    }


    @RequestMapping(value = "/notify")
    public void notify(HttpServletRequest request, HttpServletResponse response) throws IOException {
        PayResponseHandler handler = new PayResponseHandler(request);
        CallBackResult result = handler.handleCallback();
        response.getWriter().write(result.success() ? "success":"fail");
    }
}

一個處理return,一個處理notify


那麼至此,整個支付流程就抽象完成了,也基本上沒與其它業務耦合在一起。 那麼我們開篇說的訂單支付功能如何實現列?

public class OrderPayCallback implements PayCallback {


    @Override
    public CallBackResult doAfterSuccess(TradeInfo tradeInfo, Map<String, String> backParams) {

}
}

交易信息 與 所有參數都已經給到你了,那麼僅需要在此處完成的業務邏輯就可以了。




那麼以後需要調用支付模塊時僅需:
1.統寫回調業務處理類
2.在發送支付請求時用:  wrapper.setCallBackClass(OrderPayCallback.class.getName());
 

那麼就OK了。 至此整個支付流程就完全獨立了。

 好了,細節就不一一堆述了。大致思想就是如此。由於整個重構,從接手這個模塊到現在爲止也才四天,肯定還有很多可以改進和完善的地方,慢慢來吧。  以後當交易量大的時候,可以考慮做隊列,做池等手段,來解決。只不過目前 還沒有需要,所以不在本次重構的範圍內。

發佈了110 篇原創文章 · 獲贊 22 · 訪問量 63萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章