支付模块重构整理与总结

来源:互联网 发布:dsdt editor mac 1.3 编辑:程序博客网 时间:2024/06/11 09:39

    由于之前支付模块与订单耦合,也就是说如果要支付,必须要走一个订单流程,但是大家都知道:订单流程是很复杂的,而且订单只是支付的一种源头,因此就对这块代码进行了重构,解耦。


    首先,我们系统支付的接口目前集成支付宝和财富通,而且整个支付过程涉及以下几个环节:

  •  发请求,即向第三方支付接口发请求参数
  • 正常返回的接收以及处理
  • 对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了。 至此整个支付流程就完全独立了。

 好了,细节就不一一堆述了。大致思想就是如此。由于整个重构,从接手这个模块到现在为止也才四天,肯定还有很多可以改进和完善的地方,慢慢来吧。  以后当交易量大的时候,可以考虑做队列,做池等手段,来解决。只不过目前 还没有需要,所以不在本次重构的范围内。