支付寶api指南
1.業務模型分析
通過支付寶提供的Webservice接口,可以調用支付寶(https://www.alipay.com)提供的服務,這些服務在中有詳細說明。
在這些服務中,服務類型大致可以分爲以下幾類:
WebService查詢服務:
通過服務器後臺發起一個http請求,然後從服務器上返回一個Xml類型的返回結果。比如,user_query 服務,支付寶id 或者支付寶的賬戶email通過服務器後臺查詢一個支付寶會員信息。
帶有頁面跳轉的交互服務:
用戶在實現一個web功能的過程中需要調用到支付寶的幾個web頁面作爲,服務流程中的一部分。比如,user_authentication服務需要調用支付寶的一個用戶認證頁面。Custome_sign 需要使用支付的代扣協議簽訂頁面。
另外,在支付寶協作頁面跳轉回原頁面的時候,需要對參數簽名進行驗證,或者對notifyId進行驗證是否是合法的web回調請求。
2.業務場景分析
現在支付寶提供的這些服務接口,調用端只能通過查詢支付寶提供api文檔,通過api文檔進行接口開發。
偶在開發口碑卡項目中需要用到user_query,user_authentication,customer_sign等支付寶服務,以前,口碑的業務也有調用過支付寶的接口,於是我就想重用以前其他同學已經寫過的調用支付寶服務的代碼。結果發現,有很多參數已經寫死了,比如key,partnerid,還有sign的算法。所以代碼根本沒有辦法重用,只能ctrl+C加ctrl+V,而且我在開發過程中爲了解決參數簽名匹配不上的問題搞了很長時間。
所以,我認爲應該開發一個類似淘寶TOP平臺提供的API代碼接口,這個api接口的開發難度比淘寶open api 應該簡單,因爲,在和支付寶交互的數據結構比較簡單。
2.1.Api所要實現的目標
解決參數簽名,調用支付寶服務過程中,參數簽名的作用是保證用戶請求的參數沒有被黑客截取篡改參數值,在所有的請求中都會有參數簽名。爲了代碼實現簡單,先默認參數簽名方式爲MD5簽名。參數簽名應該像面向切面編程一樣,在開發一個具體的結果過程中程序員不用關心參數簽名,這也是做這個框架的重要意義所在。當初,我照着api文檔的時候爲了參數簽名真是折騰了好長時間,爲了避免其他同學重蹈我的覆轍。必須要將參數簽名封裝成一個模塊能夠重複調用。
2.2.支持多種字符集
這個API框架能夠在多種字符集環境下使用,所以這個框架要能夠支持UTF8,GBK等多種環境,只需要簡單配置就能切換。
2.3.新功能容易擴充
現在實現的接口還只是支付寶提供的所有web服務中的小部分,但是已經涵蓋到了所有前面提到的所有種類的服務(服務器後臺請求,協同頁面),如果想通過此框架實現其餘的頁面只需要實現少部分代碼就能實現功能。
3. 實現
3.1.整體類圖
3.2.核心類
3.2.1 BasicAlipayToolkit
該類是所有支付寶服務的基類,他提供了一些所有服務接口都要調用的方法。
方法有:
urlEncode()
將傳輸的值進行編碼,按照GBK或者UTF8或者其他編碼格式進行編碼。
sign()
將傳輸的參數集合進行簽名
getPartner()
取得支付寶分配給用戶的partnerid
3.2.2.AlipayApply
支付寶的所有web服務都是通過http協議請求發送的,通過這個類可以通過用戶設置的一些基本的參數組裝出發送給支付寶的http的url,如果當請求是在頁面中需要使用標籤來發送的話,就可以通過buildSubmitForm方法生成一個html的form表單。
這些方法有:
prepareParamMap()
將除partner,_input_charset,service參數字段放到map的數據結構中。
buildSubmitForm()
創建出一個from表單,可以在html頁面中使用,用於在想支付寶發起請求。
getPayGateWay()
取得支付寶請求地址,如https://www.alipay.com/cooperate/gateway.do
buildHref()
取得向支付寶請求的url。
3.2.3 BasicWebServiceApply
getMapResult()
解析支付寶遠端的返回的XML結果,封裝成一個pojo對象。
getValueElementName()
設置支付寶返回結果中代表結果值的xml節點名稱。
createResult()
創建一個遠端結果對象。
resultCallback()
返回一個結果之後設置返回結果對象的值。
3.2.4 Profile,DefaultProfile
支付寶服務請求的用戶信息封裝,能夠取到PartnerId和getGateway,不同的用戶需要通過調用。
[java] view plaincopy
Profile profile = new DefaultProfile("adfasdfasdq24234sdf3434","http://aliapi.alipay.net/gateway.do"
,"2088101010199999");
BasicAlipayToolkit.setProfile(profile);
3.3. 時序圖分析
支付寶提供了兩種服務,webService和頁面協作,根據兩種服務類型,選取兩個服務分析一下服務的流程。
3.3.1 user_query服務
單元測試Test啓動,調用BasicAlipayToolkit的靜態方法setProfile()設置連接需要用到的key,gateway,和partnerid
調用者設置查詢參數,然後調用getAlipayPojo方法,向支付寶發起查詢請求。
支付寶返回查詢結果對象AlipayQuery調用getMapResult()生成查詢結果
封裝查詢結果的過程中先要生成包裝查詢結果的對象。
創建查詢結果
根據支付寶返回的結果設置查詢結果是否成功
如果查詢有錯誤的話設置錯誤編碼
沒夠成功拿到查詢結果,設置查詢結果對象的屬性值
3.4. 功能說明
3.4.1.支付寶會員登陸驗證
用戶需要通過支付寶認證用戶信息,代碼如下:
[java] view plaincopy
AlipayAuthenticateCooperate cooperate = return new AlipayAuthenticateCooperate(
"http://localhost/callbackUrl.html",
CharSet.GBK);
在頁面上打印超鏈接
[xhtml] view plaincopy
<a href="<s:property escape="false" value="alipayAuthenticateCooperate.buildHref()" />">支付寶認證登錄</a>
在頁面上打印一個form表單
[xhtml] view plaincopy
<s:property escape="false" value="alipayAuthenticateCooperate.buildSubmitForm(‘提交’)" />
當用戶在支付寶用戶認證之後,跳轉到原網站頁面,需要取得支付寶會員信息:
[java] view plaincopy
HttpServletRequest request = ServletActionContext.getRequest();
AlipayAuthenticateCallback authenticateCallback = new AlipayAuthenticateCallback(
CharSet.UTF8);
AlipayResult alipayResult = authenticateCallback
.getAlipayResult(request.getParameterMap());
if (!alipayResult.isSuccess()) {
throw new BizException("alipayResult is false,error code:"
+ alipayResult.getErrorCode() + "request url:["
+ request.getQueryString() + "]");}
String alipayid = alipayResult.getUserid();
AlipayAccount account = new AlipayAccount(alipayid, bizContext);
account.setAccountId(alipayid + "0156");
account.setEmail(alipayResult.getEmail());
account.setMobile(alipayResult.getMobile());
注意:在使用該服務api代碼必要代碼中設置的CharSet需要和當前tomcat URLencode要一致,否則,跳轉會原頁面會有sign不一致的問題。
3.4.2. 即時到帳
在頁面中要發起一個即時到帳的請求,代碼如下:
[java] view plaincopy
AlipayPayment patment = return new AlipayPayment("[email protected]",
"http://localhost/return_url.html",
"http://localhost/notify_url.html",
"http://localhost/showurl.html",
"123456", // out_trade_no
12f, //單價
3,//數量
CharSet.GBK,
Configuration.GetConfig("sellerEmail")// 賣家email賬戶
);
// 設置跳轉的url
action.setRedirectUrl(String.valueOf(payment.buildHref()));
用戶在支付寶完成即時到帳功能之後跳轉到原頁面
[java] view plaincopy
import com.koubei.kac.alipaytaobaocooperate.CallbackValidate;
llbackValidate validate = new CallbackValidate();
HttpServletRequest request = ServletActionContext.getRequest();
// 判斷url sign 是否正確
if (!validate.isValidCallbackApply(request.getParameterMap())) {
getLog().error(
"is invalid callback request from alipay notfiyid:"
+ this.getNotify_id()
+ " out_trade_no[delivery_detail PK]:"
+ this.out_trade_no);
// 跳轉到錯誤頁面
return getErrorResult();
}
if (!validate.isValidNotify(this.notify_id)) {
getLog().error(
"not valid notifyid, notify_id:" + this.notify_id
+ " out_trade_no:" + out_trade_no
+ " trade_status:" + trade_status);
// 非法請求跳轉到錯誤頁面
return getErrorResult();
}
3.4.3.CAE代扣協議
[java] view plaincopy
CAEAlipayCooperate cooperate =
return new CAEAlipayCooperate(
"http://localhost/callback.html",
"http://localhost/notify.html",
"[email protected]", "CAE業務提交", CharSet.UTF8,bizCode
);
cooperate. buildHref();//生成cae代扣協議的url
// 結果:
/**
http://aliapi.alipay.net/gateway.do?sign_type=MD5&sign=d78ce0c98deabe389c0b05dabc1ae1c9&_input_charset=utf-8&customer_email=mozhenghua19811109%40<br />
126.com¬ify_url=http%3A%2F%2Fhangzhou.koubei.com%2Fka%2Fnotify.html&service=customer_sign&partner=2088101010199999&type_code=123456&return_url<br />
=http%3A%2F%2Fhangzhou.koubei.com%2Fka%2Fcallback.html
*/
cooperate.buildSubmitForm();//生成cae代扣協議提交的form表單
//結果:
/**
<form method="post" action="http://aliapi.alipay.net/gateway.do?_input_charset=utf-8"> <input type="hidden" name="_input_charset" value="utf-8" /><br />
<input type="hidden" name="customer_email" value="[email protected]" /><br />
<input type="hidden" name="notify_url" value="http://localhost/notify.html"/><br />
<input type="hidden" name="service" value="customer_sign" /><br />
<input type="hidden" name="partner" value="2088101010159999" /><br />
<input type="hidden" name="type_code" value="123456" /><br />
<input type="hidden" name="return_url" value="http://localhost/callback.html" /><br />
<input type="hidden" name="sign" value="d78ce0c98deabe389c0b05dabc1ae1c9" /> <input type="hidden" name="sign_type" value="MD5" /> <input class<br />
="submitBtn" type="submit" value="CAE業務提交"/> </form>
*/
用戶完成支付寶代扣協議跳轉到口碑頁面
[java] view plaincopy
HttpServletRequest request = ServletActionContext.getRequest();
CallbackValidate validate = new CallbackValidate();
boolean isValidApply = validate.isValidCallbackApply(request
.getParameterMap(), new CallbackValidate.KeyIgnorJudgement() {
@Override
public boolean ignor(String key) {
return "sign".equalsIgnoreCase(key)
|| "sign_type".equalsIgnoreCase(key)
|| "action".equalsIgnoreCase(key);
}
});
// 非法請求
if (!isValidApply) {
return ERROR;
}
// 測試該請求是否合法
boolean isSuccess = "T".equalsIgnoreCase(request
.getParameter("is_success"));
if (!isSuccess) {
return "caesingerror";
}