Android開發:app工程整合銀聯支付功能(伺服器端)
2016年5月6日有更新,請參考第三部分。
一.功能描述
因為是自己開發了一個app應用,沒資格去申請微信支付和支付寶支付,於是就採用了銀聯支付功能,銀聯支付分為了兩種環境:測試環境和生產環境,一般前期開發的時候都是使用測試環境,資料都是測試資料,不會發生真實交易。第一次做Android專案+IDE為Android Studio+第一次整合支付功能,所以個人覺得整個整合過程可能有點複雜,而且銀聯支付產品眾多:閘道器支付產品、手機控制元件支付、手機網頁支付等等,第一次看的時候真是雲裡霧裡,不知道選哪個,不知道他們的區別,最後自己選擇了手機控制元件支付,先試試,光看沒有用。自己做了多少寫多少,好記性不如爛筆頭,就怕後面想記錄的時候忘記了前面的過程。
二.實現過程
2.1下載銀聯支付SDK和Demo
第一步:註冊;
第二步:然後在幫助中心介面的產品分類下載裡選擇手機控制元件支付;
第三步:下載安卓版的開發包。
(2)下載的檔案如下
2.2整合過程
個人建議可以先把伺服器端的工程跑一下,這樣結合程式碼的時候就很容易明白怎麼整合到自己的工程裡了。所以這一小部分內容是官方Demo的執行情況,需要修改的配置很少,但是還是有小地方需要調整一下。
(1)先試官方Demo
- 將工程匯入
- 修改acp_sdk_properties的配置
將這三個路徑修改為測試環境證書的路徑(證書在assets資料夾下),可以使用相對路徑或者絕對路徑都行,下面圖中是絕對路徑,我把assets/測試環境證書下的三個檔案移動到了C盤。
我的測試環境證書地址:
- Run一個試試
啟動tomcat過程中比較關鍵的一處就是
- 首頁
- 獲取tn
最後列印的報文
(2)整合到自己的工程裡
先講伺服器端,因為自己也才完成了這部分工作。運行了官方Demo以及相關說明文件後,整體思路其實就有了。
首先試試配置!!!
- jar包
將Demo工程lib中的jar包複製到自己工程的lib裡(如果已經有jar包了,就不需要複製了)
- 修改acp_sdk_propertise和log4j.properties
(主要是一些路徑的修改,因為我的伺服器上只有C盤,所以我必須得改,acp_sdk_properties的修改見(1)執行Demo部分)
- 匯入相關java檔案
之所以寫用import的方式是為了少出現亂碼的問題,見下圖
第一部分 controller對應於Demo中的ACPSample_AppServer\src\com\unionpay\acp\demo,其中PayController是我自己寫的Controller。
第二部分model對應於Demo中的ACPSample_AppServer\src\com\unionpay\acp\sdk
第三部分的兩個java檔案對應於Demo中的\ACPSample_AppServer\src\web中的兩個java檔案
- web.xml配置
增加
<servlet>
<servlet-name>autoLoadServlet</servlet-name>
<servlet-class>com.XXX.component.pay.AutoLoadServlet</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>autoLoadServlet</servlet-name>
<url-pattern>/autoLoadServlet</url-pattern>
</servlet-mapping>
ok,配置過程就算完事了,現在就該寫PayController來接受請求了。
- PayController控制器
其實就是Form05_6_2_AppConsume.java中的程式碼,因為使用是SSM架構,就改了改架構而已。
@Controller
public class PayController extends BasicController{
@RequestMapping(value = "/pay/pay")
@ResponseBody
public void pay(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
request.setCharacterEncoding(DemoBase.encoding_UTF8);//
response.setContentType("text/html; charset="+ DemoBase.encoding_UTF8);//這兩句是我臨時加的,因為出現了亂碼
Map<String, String> contentData = new HashMap<String, String>();
/***銀聯全渠道系統,產品引數,除了encoding自行選擇外其他不需修改***/
contentData.put("version", DemoBase.version); //版本號 全渠道預設值
contentData.put("encoding", DemoBase.encoding_UTF8); //字符集編碼 可以使用UTF-8,GBK兩種方式
contentData.put("signMethod", "01"); //簽名方法 目前只支援01:RSA方式證書加密
contentData.put("txnType", "01"); //交易型別 01:消費
contentData.put("txnSubType", "01"); //交易子類 01:消費
contentData.put("bizType", "000201"); //填寫000201
contentData.put("channelType", "08"); //渠道型別 08手機
String merId = request.getParameter("merId");
String txnAmt = request.getParameter("txnAmt");
String orderId = request.getParameter("orderId");
String txnTime = request.getParameter("txnTime");
/***商戶接入引數***/
contentData.put("merId", merId); //商戶號碼,請改成自己申請的商戶號或者open上註冊得來的777商戶號測試
contentData.put("accessType", "0"); //接入型別,商戶接入填0 ,不需修改(0:直連商戶, 1: 收單機構 2:平臺商戶)
contentData.put("orderId", orderId); //商戶訂單號,8-40位數字字母,不能含“-”或“_”,可以自行定製規則
contentData.put("txnTime", txnTime); //訂單傳送時間,取系統時間,格式為YYYYMMDDhhmmss,必須取當前時間,否則會報txnTime無效
contentData.put("accType", "01"); //賬號型別 01:銀行卡02:存摺03:IC卡帳號型別(卡介質)
contentData.put("txnAmt", txnAmt); //交易金額 單位為分,不能帶小數點
contentData.put("currencyCode", "156"); //境內商戶固定 156 人民幣
//contentData.put("reqReserved", "透傳欄位"); //商戶自定義保留域,交易應答時會原樣返回
//後臺通知地址(需設定為外網能訪問 http https均可),支付成功後銀聯會自動將非同步通知報文post到商戶上送的該地址,【支付失敗的交易銀聯不會發送後臺通知】
//後臺通知引數詳見open.unionpay.com幫助中心 下載 產品介面規範 閘道器支付產品介面規範 消費交易 商戶通知
//注意:1.需設定為外網能訪問,否則收不到通知 2.http https均可 3.收單後臺通知後需要10秒內返回http200或302狀態碼
// 4.如果銀聯通知伺服器傳送通知後10秒內未收到返回狀態碼或者應答碼非http200或302,那麼銀聯會間隔一段時間再次傳送。總共傳送5次,銀聯後續間隔1、2、4、5 分鐘後會再次通知。
// 5.後臺通知地址如果上送了帶有?的引數,例如:http://abc/web?a=b&c=d 在後臺通知處理程式驗證簽名之前需要編寫邏輯將這些欄位去掉再驗籤,否則將會驗籤失敗
contentData.put("backUrl", DemoBase.backUrl);//[其實還沒搞明白這個地址的作用]
/**對請求引數進行簽名併發送http post請求,接收同步應答報文**/
Map<String, String> reqData = AcpService.sign(contentData,DemoBase.encoding_UTF8); //報文中certId,signature的值是在signData方法中獲取並自動賦值的,只要證書配置正確即可。
String requestAppUrl = SDKConfig.getConfig().getAppRequestUrl(); //交易請求url從配置檔案讀取對應屬性檔案acp_sdk.properties中的 acpsdk.backTransUrl
Map<String, String> rspData = AcpService.post(reqData,requestAppUrl,DemoBase.encoding_UTF8); //傳送請求報文並接受同步應答(預設連線超時時間30秒,讀取返回結果超時時間30秒);這裡呼叫signData之後,呼叫submitUrl之前不能對submitFromData中的鍵值對做任何修改,如果修改會導致驗籤不通過
/**對應答碼的處理,請根據您的業務邏輯來編寫程式,以下應答碼處理邏輯僅供參考------------->**/
//應答碼規範參考open.unionpay.com幫助中心 下載 產品介面規範 《平臺接入介面規範-第5部分-附錄》
if(!rspData.isEmpty()){
if(AcpService.validate(rspData, DemoBase.encoding_UTF8)){
LogUtil.writeLog("驗證簽名成功");
String respCode = rspData.get("respCode") ;
if(("00").equals(respCode)){
//成功,獲取tn號
//String tn = resmap.get("tn");//這裡應該是rspData.get("tn");
//TODO
}else{
//其他應答碼為失敗請排查原因或做失敗處理
//TODO
}
}else{
LogUtil.writeErrorLog("驗證簽名失敗");
//TODO 檢查驗證簽名失敗的原因
}
}else{
//未返回正確的http狀態
LogUtil.writeErrorLog("未獲取到返回報文或返回http狀態碼非200");
}
String reqMessage = DemoBase.genHtmlResult(reqData);
String rspMessage = DemoBase.genHtmlResult(rspData);
try {
response.getWriter().write("請求報文:<br/>"+reqMessage+"<br/>" + "應答報文:</br>"+rspMessage+"");
//response.getOutputStream().write(("請求報文:<br/>"+reqMessage+"<br/>" + "應答報文:</br>"+rspMessage+"").getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
- 執行
目前就把伺服器端的tn獲取搭好了,還有退款退貨等等以及客戶端的搭建,接著搞~~
2016年5月6日補充
伺服器端還應該有“後臺通知接收處理”部分。可參見Demo中ACPSample_AppServer\src\com\unionpay\acp\demo\BackRcvResponse.java。這部分的功能很重要:見下圖所示(銀聯支付流程圖)
第七步是銀聯後臺將支付結果返回給我們的伺服器端,我們將根據返回結果更新訂單的狀態,請注意:雖然第八步是將客戶端的支付控制元件也會支付結果,但是能作為訂單支付結果的只有後臺的通知,官方文件中有一處說的有歧義,客服是這樣說的:
關於客戶端支付結果+驗籤,在下一篇文件中說明。
3.3後臺通知接收處理配置
(1)修改DemoBase.java中的backUrl
請注意:這個backUrl必須填寫真實ip地址,迴路地址不行、localhost不行,銀聯後臺返回通知必須能post到你的backUrl。
(2)後臺通知處理部分
/**
* 後臺通知處理
* @param sign
* @param request
* @param response
*/
@RequestMapping(value = "/pay/backRcvResponse")
@ResponseBody
public void backRcvResponse(HttpServletRequest request, HttpServletResponse response)
{
System.out.println("後臺通知驗籤開始");
//return AcpService.validateAppResponse(sign, DemoBase.encoding_UTF8);
//System.out.println("驗籤開始");
String encoding = request.getParameter(SDKConstants.param_encoding);
// 獲取銀聯通知伺服器傳送的後臺通知引數
Map<String, String> reqParam = Tool.getAllRequestParam(request);
LogUtil.printRequestLog(reqParam);
Map<String, String> valideData = null;
try
{
if (null != reqParam && !reqParam.isEmpty()) {
Iterator<Entry<String, String>> it = reqParam.entrySet().iterator();
valideData = new HashMap<String, String>(reqParam.size());
while (it.hasNext()) {
Entry<String, String> e = it.next();
String key = (String) e.getKey();
String value = (String) e.getValue();
value = new String(value.getBytes(encoding), encoding);
valideData.put(key, value);
}
}
//重要!驗證簽名前不要修改reqParam中的鍵值對的內容,否則會驗籤不過
if (!AcpService.validate(valideData, encoding)) {
LogUtil.writeLog("驗證簽名結果[失敗].");
//驗籤失敗,需解決驗籤問題
} else {
LogUtil.writeLog("驗證簽名結果[成功].");
//【注:為了安全驗籤成功才應該寫商戶的成功處理邏輯】交易成功,更新商戶訂單狀態
//valideData裡封裝了很多資料,可參考官方文件,做相應後續處理
}
}
LogUtil.writeLog("BackRcvResponse接收後臺通知結束");
//返回給銀聯伺服器http 200 狀態碼
response.getWriter().print("ok");
}
catch(Exception e){}
}
ok,backUrl配置正確,那麼進行支付後,商戶後臺就會得到銀聯後臺的支付通知,如下圖:
這部分工作不能少,一定要記住後臺通知處理的作用:真正作為接收訂單支付結果的地方。