由於之前支付模塊與訂單耦合,也就是說如果要支付,必須要走一個訂單流程,但是大家都知道:訂單流程是很複雜的,而且訂單只是支付的一種源頭,因此就對這塊代碼進行了重構,解耦。
首先,我們系統支付的接口目前集成支付寶和財富通,而且整個支付過程涉及以下幾個環節:
- 發請求,即向第三方支付接口發請求參數
- 正常返回的接收以及處理
- 對Notify請求的接收和處理。
首先,我們來抽象一下發請求的部分:
- 封裝請求參數
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。支付包括的參數: 交易金額,交易流水號,還有你的支付方式(財富通,支付寶,銀行也隸屬於支付寶,因爲支付寶兼容銀行),還有本次交易成功後的回調處理類。 - 封裝發請求的類
我們抽象了一個父類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了。 至此整個支付流程就完全獨立了。
好了,細節就不一一堆述了。大致思想就是如此。由於整個重構,從接手這個模塊到現在爲止也才四天,肯定還有很多可以改進和完善的地方,慢慢來吧。 以後當交易量大的時候,可以考慮做隊列,做池等手段,來解決。只不過目前 還沒有需要,所以不在本次重構的範圍內。