這一節(jié)我們先寫(xiě)一個(gè)簡(jiǎn)單點(diǎn)的Demo來(lái)測(cè)試易寶支付的流程,熟悉這個(gè)流程后,再做實(shí)際的開(kāi)發(fā),因?yàn)槭且粋€(gè)Demo,所以我沒(méi)有考慮一些設(shè)計(jì)模式的東西,就是直接實(shí)現(xiàn)支付功能。實(shí)現(xiàn)支付功能需要易寶給我們提供的API。那么問(wèn)題來(lái)了,使用第三方支付平臺(tái)最主要的一件事就是獲取該平臺(tái)的API,我們首先得獲取他們的API以及開(kāi)發(fā)文檔,然后才可以做進(jìn)一步的開(kāi)發(fā)。
1. 獲取易寶的API
獲取API的第一步,要在易寶上注冊(cè)一個(gè)賬號(hào),這個(gè)賬號(hào)是商家的賬號(hào),后面買(mǎi)家付款后,會(huì)將錢(qián)款存入該賬號(hào)中,然后商家自己提取到銀行卡,易寶在提取過(guò)程中收取一定的手續(xù)費(fèi)。這就是易寶的盈利模式。但是注冊(cè)成功需要前提,那就是自己得有一個(gè)網(wǎng)站,或者是一個(gè)公司,吧啦吧啦等東西,反正就是你得有資格申請(qǐng),這點(diǎn)易寶會(huì)審核的,滿足了才會(huì)允許你注冊(cè),才會(huì)給你提供他們的接口,不是所有人都可以注冊(cè)的。我用的也是別人注冊(cè)好的,我自己啥也沒(méi)有……也沒(méi)法注冊(cè)……屌絲一個(gè),大家懂的~但是一般在公司里開(kāi)發(fā)的話,就不會(huì)存在這個(gè)問(wèn)題,賬號(hào)肯定都是有的,最重要的是要掌握開(kāi)發(fā)流程和相關(guān)技術(shù)~
2. 測(cè)試支付流程
有了官方提供的API和技術(shù)文檔后,就可以著手開(kāi)發(fā)了,在這里主要寫(xiě)一個(gè)簡(jiǎn)單的demo來(lái)測(cè)試一下易寶支付的流程,demo的結(jié)構(gòu)很簡(jiǎn)單,一個(gè)servlet,一個(gè)filter,兩個(gè)jsp頁(yè)面和一個(gè)加密的工具類。servlet與易寶服務(wù)器端打交道,我們做一些跟易寶接口相關(guān)的處理,filter是用來(lái)處理可能出現(xiàn)的中文亂碼問(wèn)題,兩個(gè)jsp中一個(gè)是前臺(tái)頁(yè)面。
我們先來(lái)分析一下支付請(qǐng)求的過(guò)程,如下所示:
好了,下面我們具體分析一下demo中的相關(guān)代碼:
2.1 前臺(tái)測(cè)試頁(yè)面
首先看一下前臺(tái)頁(yè)面index.jsp的具體代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<%@ page language="java" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> < html > < head > < title >前臺(tái)首頁(yè)</ title > </ head > < body > < h1 >在線支付演示</ h1 > < form action = "${pageContext.request.contextPath }/servlet/PayServlet" method = "post" > 此次購(gòu)物訂單編號(hào)< input type = "text" name = "p2_Order" />< br > money< input type = "text" name = "p3_Amt" value = "0.01" />< br > 工商銀行< input type = "radio" value = "ICBC-NET" name = "pd_FrpId" > 建設(shè)銀行< input type = "radio" value = "CCB-NET" name = "pd_FrpId" >< br > < input type = "submit" value = "submit" /> < input type = "hidden" value = "pay" name = "status" /> </ form > </ body > </ html > |
從上面的jsp頁(yè)面中可以看出,這些input標(biāo)簽中的name屬性值都很奇怪,pi_功能(i=0,1,2,…,9),當(dāng)然i還有其他的值,這得參照易寶的官方文檔,這些name表示相對(duì)應(yīng)的屬性,到時(shí)候會(huì)傳到sevlet處理,關(guān)于這些屬性值,我截了個(gè)圖,如下:
這些參數(shù)名有些在實(shí)際項(xiàng)目中是前臺(tái)傳進(jìn)來(lái)的,比如上面寫(xiě)的訂單號(hào),要付多少錢(qián),這些在訂單確認(rèn)的時(shí)候都會(huì)帶過(guò)去,那么其他參數(shù),必填的話,需要在servlet里指定好,非必填字段的話,就可以為空,這里的空不是null,而是”“,后面servlet中會(huì)提到。
再看看兩個(gè)銀行中對(duì)應(yīng)的value值也是固定的,易寶會(huì)提供它所支持的所有銀行的value值,這些都是固定的,不能修改的。這里就寫(xiě)兩個(gè)銀行測(cè)試一下效果。
最后那個(gè)隱藏字段是用來(lái)在servlet中做判斷的,是支付還是支付成功后的返回,下面在sevlet中會(huì)說(shuō)明。
2.2 Servlet處理請(qǐng)求
servlet主要處理與易寶的相關(guān)請(qǐng)求,里面有兩個(gè)部分的內(nèi)容,一部分是向易寶發(fā)送明文和密文,另一部分是判斷易寶發(fā)過(guò)來(lái)的明文和密文,我們看看demo中具體的實(shí)現(xiàn)代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
public class PayServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String status = request.getParameter( "status" ); if (status.equals( "pay" )) { //index.jsp中隱藏字段傳來(lái)的是pay,所以處理支付這部分 // 加密的密鑰,用在加密算法中,由支付中介提供,每個(gè)商家獨(dú)一無(wú)二的 String keyValue = "w0P75wMZ203fr46r5i70V556WHFa94j14yW5J6vuh4yo3nRl5jsqF3c41677" ; // 1: 給參數(shù)賦值,這些參數(shù)(即明文)都是易寶官方提供的文檔中所定義的,名字我們不能改 String p0_Cmd = formatString( "Buy" ); String p1_MerId = formatString( "10000940764" ); String p2_Order = formatString(request.getParameter( "p2_Order" )); String p3_Amt = formatString(request.getParameter( "p3_Amt" )); String p4_Cur = formatString( "CNY" ); String p5_Pid = "" ; String p6_Pcat = "" ; String p7_Pdesc = "" ; String p8_Url = "http://www.tongji.edu.cn" ;//這是支付成功后跳轉(zhuǎn)到的頁(yè)面,可以設(shè)為商城首頁(yè),這個(gè)demo就用同濟(jì)大學(xué)主頁(yè)好了…… String p9_SAF = "0" ; String pa_MP = "" ; String pd_FrpId = formatString(request.getParameter( "pd_FrpId" )); pd_FrpId = pd_FrpId.toUpperCase(); String pr_NeedResponse = "0" ; String hmac = formatString( "" ); //hmac是用來(lái)存儲(chǔ)密文的 /*上面所有的明文都用都用formatString方法包裝了一下,該方法在下面,主要是將null轉(zhuǎn)換成"" *因?yàn)閚ull是無(wú)法轉(zhuǎn)換成密文的*/ // 解決數(shù)據(jù)安全性問(wèn)題: 把明文加密--->密文 然后把明文和密文都交給易寶 // 易寶拿到數(shù)據(jù)后,把傳過(guò)來(lái)的明文加密, 和傳過(guò)來(lái)密文比較, // 如果相等數(shù)據(jù)沒(méi)有被篡改 (商家與易寶加密時(shí)都用的是相同key) // 把明文數(shù)據(jù)追加到StringBuffer,注意追加順序不能改,否則生成的密文會(huì)不同的, // 要嚴(yán)格按照易寶的官方文檔說(shuō)名來(lái)寫(xiě)才行,因?yàn)橐讓毮沁吘褪歉鶕?jù)文檔中的順序追加的 StringBuffer infoBuffer = new StringBuffer(); infoBuffer.append(p0_Cmd); infoBuffer.append(p1_MerId); infoBuffer.append(p2_Order); infoBuffer.append(p3_Amt); infoBuffer.append(p4_Cur); infoBuffer.append(p5_Pid); infoBuffer.append(p6_Pcat); infoBuffer.append(p7_Pdesc); infoBuffer.append(p8_Url); infoBuffer.append(p9_SAF); infoBuffer.append(pa_MP); infoBuffer.append(pd_FrpId); infoBuffer.append(pr_NeedResponse); // 加密后的密文存儲(chǔ)到了hmac中,加密算法易寶會(huì)提供的,因?yàn)樗沁呉驳糜孟嗤乃惴?/code> hmac = DigestUtil.hmacSign(infoBuffer.toString(), keyValue); // 把明文和密文都存儲(chǔ)到request.setAttribute中 request.setAttribute( "p0_Cmd" , p0_Cmd); request.setAttribute( "p1_MerId" , p1_MerId); request.setAttribute( "p2_Order" , p2_Order); request.setAttribute( "p3_Amt" , p3_Amt); request.setAttribute( "p4_Cur" , p4_Cur); request.setAttribute( "p5_Pid" , p5_Pid); request.setAttribute( "p6_Pcat" , p6_Pcat); request.setAttribute( "p7_Pdesc" , p7_Pdesc); request.setAttribute( "p8_Url" , p8_Url); request.setAttribute( "p9_SAF" , p9_SAF); request.setAttribute( "pa_MP" , pa_MP); request.setAttribute( "pd_FrpId" , pd_FrpId); request.setAttribute( "pr_NeedResponse" , pr_NeedResponse); request.setAttribute( "hmac" , hmac); System.out.println( "hmac-->" + hmac); //跳轉(zhuǎn)到reqpay.jsp中,將這些信息提交到易寶 request.getRequestDispatcher( "/reqpay.jsp" ).forward(request, response); } else if (status.equals( "success" )) { //易寶那邊傳來(lái)的是success,處理返回驗(yàn)證部分 PrintWriter out = response.getWriter(); String keyValue = "w0P75wMZ203fr46r5i70V556WHFa94j14yW5J6vuh4yo3nRl5jsqF3c41677" ; // 獲取所有的明文 String r0_Cmd = formatString(request.getParameter( "r0_Cmd" )); String p1_MerId = request.getParameter( "p1_MerId" ); String r1_Code = formatString(request.getParameter( "r1_Code" )); String r2_TrxId = formatString(request.getParameter( "r2_TrxId" )); String r3_Amt = formatString(request.getParameter( "r3_Amt" )); String r4_Cur = formatString(request.getParameter( "r4_Cur" )); String r5_Pid = new String(formatString( request.getParameter( "r5_Pid" )).getBytes( "iso-8859-1" ), "UTF-8" ); String r6_Order = formatString(request.getParameter( "r6_Order" )); String r7_Uid = formatString(request.getParameter( "r7_Uid" )); String r8_MP = new String(formatString( request.getParameter( "r8_MP" )).getBytes( "iso-8859-1" ), "UTF-8" ); String r9_BType = formatString(request.getParameter( "r9_BType" )); // 對(duì)明文進(jìn)行數(shù)據(jù)追加 String hmac = formatString(request.getParameter( "hmac" )); StringBuffer infoBuffer = new StringBuffer(); infoBuffer.append(p1_MerId); infoBuffer.append(r0_Cmd); infoBuffer.append(r1_Code); infoBuffer.append(r2_TrxId); infoBuffer.append(r3_Amt); infoBuffer.append(r4_Cur); infoBuffer.append(r5_Pid); infoBuffer.append(r6_Order); infoBuffer.append(r7_Uid); infoBuffer.append(r8_MP); infoBuffer.append(r9_BType); // 對(duì)返回的明文進(jìn)行加密 String md5 = DigestUtil.hmacSign(infoBuffer.toString(), keyValue); // 判斷加密的密文與傳過(guò)來(lái)的數(shù)據(jù)簽名是否相等 boolean isOK = md5.equals(hmac); if (isOK && r1_Code.equals( "1" )) { //r1_Code為1表示成功 //把支付成功的訂單狀態(tài)改成已支付,并個(gè)給用戶顯示支付成功信息 //調(diào)用郵件服務(wù)接口,短信發(fā)送服務(wù)等 //這里就打印一句話唄~ out.println( "訂單編號(hào)為:" + r6_Order + "支付金額為:" + r3_Amt); } else { out.println( "fail !!!!" ); } } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } String formatString(String text) { if (text == null ) { return "" ; } return text; } } |
2.3 加密算法
明文轉(zhuǎn)密文所用到的加密算法由易寶提供,我們只需要用它將明文轉(zhuǎn)為密文即可,算法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
public class DigestUtil { private static String encodingCharset = "UTF-8" ; public static String hmacSign(String aValue, String aKey) { byte k_ipad[] = new byte [ 64 ]; byte k_opad[] = new byte [ 64 ]; byte keyb[]; byte value[]; try { keyb = aKey.getBytes(encodingCharset); value = aValue.getBytes(encodingCharset); } catch (UnsupportedEncodingException e) { keyb = aKey.getBytes(); value = aValue.getBytes(); } Arrays.fill(k_ipad, keyb.length, 64 , ( byte ) 54 ); Arrays.fill(k_opad, keyb.length, 64 , ( byte ) 92 ); for ( int i = 0 ; i < keyb.length; i++) { k_ipad[i] = ( byte ) (keyb[i] ^ 0x36 ); k_opad[i] = ( byte ) (keyb[i] ^ 0x5c ); } MessageDigest md = null ; try { md = MessageDigest.getInstance( "MD5" ); } catch (NoSuchAlgorithmException e) { return null ; } md.update(k_ipad); md.update(value); byte dg[] = md.digest(); md.reset(); md.update(k_opad); md.update(dg, 0 , 16 ); dg = md.digest(); return toHex(dg); } public static String toHex( byte input[]) { if (input == null ) return null ; StringBuffer output = new StringBuffer(input.length * 2 ); for ( int i = 0 ; i < input.length; i++) { int current = input[i] & 0xff ; if (current < 16 ) output.append( "0" ); output.append(Integer.toString(current, 16 )); } return output.toString(); } public static String getHmac(String[] args, String key) { if (args == null || args.length == 0 ) { return ( null ); } StringBuffer str = new StringBuffer(); for ( int i = 0 ; i < args.length; i++) { str.append(args[i]); } return (hmacSign(str.toString(), key)); } /** * @param aValue * @return */ public static String digest(String aValue) { aValue = aValue.trim(); byte value[]; try { value = aValue.getBytes(encodingCharset); } catch (UnsupportedEncodingException e) { value = aValue.getBytes(); } MessageDigest md = null ; try { md = MessageDigest.getInstance( "SHA" ); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); return null ; } return toHex(md.digest(value)); } //我自己用來(lái)測(cè)試的 public static void main(String[] args) { // 參數(shù)1: 明文(要加密的數(shù)據(jù)) 參數(shù)2: 密鑰 System.out.println(DigestUtil.hmacSign( "11111" , "abc" )); System.out.println(DigestUtil.hmacSign( "11111" , "abd" )); // 解決數(shù)據(jù)安全性問(wèn)題: 把明文加密--->密文 然后把明文和密文都交給易寶 // 易寶拿到數(shù)據(jù)后,把傳過(guò)來(lái)的明文加密, 和傳過(guò)來(lái)密文比較,如果相等數(shù)據(jù)沒(méi)有被篡改 (商家與易寶加密時(shí)都用的是相同key) } } |
加密算法也不去過(guò)多的研究了,好像是md5二代加密算法,反正把明文扔進(jìn)去,肯定加密成密文就行了。下面再看一下reqpay.jsp頁(yè)面:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
<%@page language="java" contentType="text/html;charset=gbk"%> < html > < head > < title >To YeePay Page </ title > </ head > < body > < form name = "yeepay" action = 'https://www.yeepay.com/app-merchant-proxy/node' method = 'POST' target = "_blank" > < input type = 'hidden' name = 'p0_Cmd' value = '${requestScope.p0_Cmd}' > < input type = 'hidden' name = 'p1_MerId' value = '${requestScope.p1_MerId}' > < input type = 'hidden' name = 'p2_Order' value = '${requestScope.p2_Order}' > < input type = 'hidden' name = 'p3_Amt' value = '${requestScope.p3_Amt}' > < input type = 'hidden' name = 'p4_Cur' value = '${requestScope.p4_Cur}' > < input type = 'hidden' name = 'p5_Pid' value = '${requestScope.p5_Pid}' > < input type = 'hidden' name = 'p6_Pcat' value = '${requestScope.p6_Pcat}' > < input type = 'hidden' name = 'p7_Pdesc' value = '${requestScope.p7_Pdesc}' > < input type = 'hidden' name = 'p8_Url' value = '${requestScope.p8_Url}' > < input type = 'hidden' name = 'p9_SAF' value = '${requestScope.p9_SAF}' > < input type = 'hidden' name = 'pa_MP' value = '${requestScope.pa_MP}' > < input type = 'hidden' name = 'pd_FrpId' value = '${requestScope.pd_FrpId}' > < input type = "hidden" name = "pr_NeedResponse" value = "${requestScope.pr_NeedResponse}" > < input type = 'hidden' name = 'hmac' value = '${requestScope.hmac}' > < input type = 'submit' /> </ form > </ body > </ html > |
其實(shí)該頁(yè)面很簡(jiǎn)單,就是將明文和密文一起通過(guò)<form>
表單傳到易寶,易寶的接收url為https://www.yeepay.com/app-merchant-proxy/node
,這也是易寶官方提供的,我們寫(xiě)成這個(gè)就可以了。其實(shí)就一個(gè)submit
按鈕,點(diǎn)擊submit
按鈕就能將明文和密文提交過(guò)去了。我們看一下測(cè)試結(jié)果:
3. 測(cè)試支付結(jié)果
簡(jiǎn)陋的測(cè)試前臺(tái)index.jsp~~~:
提交后會(huì)到reqpay,jsp,點(diǎn)擊提交按鈕后的效果如下,我們將工行和建行都測(cè)一下:
支付流程都沒(méi)啥問(wèn)題,本來(lái)準(zhǔn)備去工行交個(gè)1分錢(qián)看一下支付完成后的結(jié)果,結(jié)果發(fā)現(xiàn)U盾過(guò)期了,因?yàn)楝F(xiàn)在用支付寶比較方便嘛……就沒(méi)去更新U盾了,但是我開(kāi)通過(guò)工行的e支付,所以上面那個(gè)界面中也可以使用e支付,于是我就很大方的付了1分錢(qián)~~結(jié)果如下:
然后會(huì)跳轉(zhuǎn)到我們之前指定的頁(yè)面,也就是同濟(jì)大學(xué)咯……好了,測(cè)試完成了,整個(gè)支付流程結(jié)束!
這一節(jié)主要是通過(guò)一個(gè)簡(jiǎn)單的demo測(cè)試一下,看能否和銀行的支付界面接上,現(xiàn)在測(cè)試是沒(méi)問(wèn)題的,已經(jīng)接上了,后面只要照常支付即可。簡(jiǎn)單的demo就介紹到這吧,后面就真正繼續(xù)我們之前的網(wǎng)上商城項(xiàng)目的在線支付模塊的開(kāi)發(fā)了。
原文地址:http://blog.csdn.net/eson_15/article/details/51447492
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。