javaweb实现在线支付功能

时间:2022-04-18 03:59:50

1、在线支付概述

什么是在线支付呢?没错,就是在网上花钱!大家一定有过这样的经历。但是你可能不太了解在线支付的“内情”,下面我们来了解一下!

如果你现在开始经营一个电子商务网站,用户买了东西一定要支付,你的网站一定要可以连接各大银行了,然后在各大银行支付完成后,再返回到你的网站上显示“支付成功”!

这就是今天我们要做的事情,连接银行的网银系统完成支付。说专业一点,我们称之为“开发在线支付的网关”

2、两种在线支付的方式

在线支付一共有两种方式:

*电商直接与银行对接
*电商通过第三方支付平台与银行对接

电商直接与银行对接,这也要银行同意才行,但可惜的是,银行很“牛”,不是谁想与它对接都可以的。如果你的电商每日的资金流量够大,那么银行会和你对接,因为客户支付给电商的钱都存到了银行的帐户中!但是如果资金流量小,银行不会理你的!

当小网站资金量不足时,不能与银行对接,那么它们会选择与第三方支付公司合作。大家也都明白这是些什么公司,例如:支付宝、易宝、财富通、快钱等公司是国内比较有名的。它们这些公司可以与银行对接(因为资金够多),然后小电商与它们对接!但是第三方是要求收费的!第三方一般会收取电商1%的费用,不过不会收客户的钱。

javaweb实现在线支付功能

通过上图大家可以了解到,在银行的页面上会显示出商城名称、RMB订单号、订单时间。。。,这些东西银行是怎么知道的,当然是电商传递给银行的。当电商与银行对接后,电商要给银行的页面传递银行页面需要的参数,所以银行的页面才能显示这些数据!

但是,我们的商城不能只可以对接一家银行吧!怎么也要对接BOC、CCB、ABC、ICBC四家吧!不同的银行需要的对接参数是不相同的,这说明我们在开发时要为不同的银行写不同的对接代码。这也是直接与银行对接的缺点!当然与银行直接对接也有好处,就是安全,没有手续费!

为不同的银行开发不同的代码(缺点);
安全(优点);
没有手续费(优点);
小电商银行不让对接(缺点)。

javaweb实现在线支付功能

上图中已经说明,客户在电商的网站上点击确认支付后,会定向到第三方的网站,然后再由第三方与银行对接。这说明电商要传递给第三方参数!再由第三方把参数传递给银行。这种方式的好处是:只需要针对第三方开发即可,而不用再为每家银行提供参数。为每家银行提供参数的工作是第三方的任务了。但是,第三方不老可靠的,如果第三方倒闭了,人跑了,那你的钱就没了。因为客户支付的钱没有到你的银行帐户中,而是支付到了第三方的银行帐户中,而你是在第三方有一个帐户。而且第三方还要收手续费,一般是1%,这可不是小数字啊(真黑)。

3、通过第三方在线支付规则

电商想在第三方注册商户,需要向第三方提供ICP认证。ICP经营许可证是根据国家《互联网管理办法规定》,经营性网站必须办理的网站经营许可证,没有就属于非法经营。

我们不可能因为练习就去办理ICP!所以我们无法在第三方注册商户。不过我们已经有现成的在易宝注册的商户,所以这一步就可以忽略了。

当你在易宝注册成功后,易宝会给你如下几样东西:

   在易宝的开户账号(即商户编码):10001126856
   易宝接入规范:一个chm文件
   对称加密算法类:PaymentUtil.java
   密钥:69cl522AV6q613Ii4W6u8K6XuW8vM1N6bFgyv769220IuYe9u37N4y7rI4Pl
在易宝接入规范中,我们可以查找到易宝的支付网关,其实就是一个URL,用来与易宝对接的一个网址:https://www.yeepay.com/app-merchant-proxy/node

在易宝接入规范中,还可以查找到易宝要求的参数,在电商与易宝对接时需要给支付网关传递这些参数:

正式请求地址:https://www.yeepay.com/app-merchant-proxy/node

javaweb实现在线支付功能

这些参数需要追加到URL后面。

但是要注意,这些参数的值需要加密。加密的密钥和加密算法易宝都会提供!

其中p8_Url表示当支付成功后,返回到电商的哪个页面。这说明我们需要写一个显示结果的页面。第三方在支付成功后,会重定向到我们指定的返回页面,而且还会带给我们一些参数,我们的页面需要获取这些参数,显示在页面中。下面是第三方返回的参数:

javaweb实现在线支付功能

4、开发第三方在线支付系统

步骤:

index.jsp页面:一个表单,提交到BuyServlet,表单项有:订单编号、付款金额、选择银行
BuyServlet:获取表单数据,准备连接第三方网关。因为在index.jsp页面中只给出3个参数,而第三方需要的参数有N多,页面没有给出的参数由BuyServlet补充。而且参数还需要加密,这也需要在BuyServlet中完成
BackServlet:当用户支付成功后,第三方会重定向到我们指定的返回页面,我们使用BackServlet作为返回页面,它用来接收第三方传递的参数,显示在页面中

因为已经有了在易宝的注册商号,所以我们就不用自己去注册商号了。所以这里使用易宝做为第三方支付平台来测试。因为我本人没有电商(必须通过ICP认证的电商),所以也不能在第三方注册商号。

我们现在使用的易宝商号是由传智播客提供的,巴巴运动网在易宝注册的商号。所以在测试时支付的钱都给了巴巴运动网在易宝注册的商号了。

第一步:index.jsp

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<form action="" method="post">
 订单号:<input type="text" name="p2_Order"/><br/>
 金 额:<input type="text" name="p3_Amt"/><br/>
 选择银行:<br/>
 <input type="radio" name="pd_FrpId" value="ICBC-NET-B2C"/>工商银行
 <img src="bank_img/icbc.bmp" align="middle"/>
 <input type="radio" name="pd_FrpId" value="BOC-NET-B2C"/>中国银行
 <img src="bank_img/bc.bmp" align="middle"/><br/><br/>
 <input type="radio" name="pd_FrpId" value="ABC-NET-B2C"/>农业银行
 <img src="bank_img/abc.bmp" align="middle"/>
 <input type="radio" name="pd_FrpId" value="CCB-NET-B2C"/>建设银行
 <img src="bank_img/ccb.bmp" align="middle"/><br/><br/>
 <input type="radio" name="pd_FrpId" value="BOCO-NET-B2C"/>交通银行
 <img src="bank_img/bcc.bmp" align="middle"/><br/>
 <input type="submit" value="确认支付"/>
</form>

javaweb实现在线支付功能

每个银行对应的值:

javaweb实现在线支付功能

第二步:BuyServlet.java

?
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
public class BuyServlet extends HttpServlet {
 public void doPost(HttpServletRequest request, HttpServletResponse response)
   throws ServletException, IOException {
  request.setCharacterEncoding("utf-8");
  response.setContentType("text/html;charset=utf-8");
 
  String p0_Cmd = "Buy";// 业务类型,固定值为buy,即“买”
  String p1_MerId = "10001126856";// 在易宝注册的商号
  String p2_Order = request.getParameter("p2_Order");// 订单编号
  String p3_Amt = request.getParameter("p3_Amt");// 支付的金额
  String p4_Cur = "CNY";// 交易种币,固定值为CNY,表示人民币
  String p5_Pid = "";// 商品名称
  String p6_Pcat = "";// 商品各类
  String p7_Pdesc = "";// 商品描述
  String p8_Url = "http://localhost:8080/buy/BackServlet";// 电商的返回页面,当支付成功后,易宝会重定向到这个页面
  String p9_SAF = "";// 送货地址
  String pa_MP = "";// 商品扩展信息
  String pd_FrpId = request.getParameter("pd_FrpId");// 支付通道,即选择银行
  String pr_NeedResponse = "1";// 应答机制,固定值为1
 
  // 密钥,由易宝提供,只有商户和易宝知道这个密钥。
  String keyValue = "69cl522AV6q613Ii4W6u8K6XuW8vM1N6bFgyv769220IuYe9u37N4y7rI4Pl";
 
  // 通过上面的参数、密钥、加密算法,生成hmac值
  // 参数的顺序是必须的,如果没有值也不能给出null,而应该给出空字符串。
  String hmac = PaymentUtil.buildHmac(p0_Cmd, p1_MerId, p2_Order, p3_Amt,
    p4_Cur, p5_Pid, p6_Pcat, p7_Pdesc, p8_Url, p9_SAF, pa_MP,
    pd_FrpId, pr_NeedResponse, keyValue);
 
  // 把所有参数连接到网关地址后面
  String url = "https://www.yeepay.com/app-merchant-proxy/node";
  url += "?p0_Cmd=" + p0_Cmd +
    "&p1_MerId=" + p1_MerId +
    "&p2_Order=" + p2_Order +
    "&p3_Amt=" + p3_Amt +
    "&p4_Cur=" + p4_Cur +
    "&p5_Pid=" + p5_Pid +
    "&p6_Pcat=" + p6_Pcat +
    "&p7_Pdesc=" + p7_Pdesc +
    "&p8_Url=" + p8_Url +
    "&p9_SAF=" + p9_SAF +
    "&pa_MP=" + pa_MP +
    "&pd_FrpId=" + pd_FrpId +
    "&pr_NeedResponse=" + pr_NeedResponse +
    "&hmac=" + hmac;
  System.out.println(url);
  // 重定向到网关
  response.sendRedirect(url);
 }
}

第三步:BackServlet

?
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
public class BackServlet extends HttpServlet {
 public void doGet(HttpServletRequest request, HttpServletResponse response)
   throws ServletException, IOException {
  response.setContentType("text/html;charset=utf-8");
  /*
   * 易宝会提供一系列的结果参数,我们获取其中需要的即可
   * 获取支付结果:r1_Code,1表示支付成功。
   * 获取支付金额:r3_Amt
   * 获取电商的订单号:r6_Order
   * 获取结果返回类型:r9_BType,1表示重定向返回,2表示点对点返回,
   *  但点对点我们收不到,因为我们的ip都是局域网ip。
   */
  String r1_Code = request.getParameter("r1_Code");
  String r3_Amt = request.getParameter("r3_Amt");
  String r6_Order = request.getParameter("r6_Order");
  String r9_BType = request.getParameter("r9_BType");
 
  if(r1_Code.equals("1")) {
   if(r9_BType.equals("1")) {
    response.getWriter().print("<h1>支付成功!</h1>");//其实支付不成功时根本易宝根本就不会返回到本Servlet
    response.getWriter().print("支付金额为:" + r3_Amt + "<br/>");
    response.getWriter().print("订单号为:" + r6_Order + "<br/>");
   }
  }
 }
}

易宝支付提供的获取hmac的工具类

?
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
public class PaymentUtil {
 
 private static String encodingCharset = "UTF-8";
 
 /**
  * 生成hmac方法
  *
  * @param p0_Cmd 业务类型
  * @param p1_MerId 商户编号
  * @param p2_Order 商户订单号
  * @param p3_Amt 支付金额
  * @param p4_Cur 交易币种
  * @param p5_Pid 商品名称
  * @param p6_Pcat 商品种类
  * @param p7_Pdesc 商品描述
  * @param p8_Url 商户接收支付成功数据的地址
  * @param p9_SAF 送货地址
  * @param pa_MP 商户扩展信息
  * @param pd_FrpId 银行编码
  * @param pr_NeedResponse 应答机制
  * @param keyValue 商户密钥
  * @return
  */
 public static String buildHmac(String p0_Cmd,String p1_MerId,
   String p2_Order, String p3_Amt, String p4_Cur,String p5_Pid, String p6_Pcat,
   String p7_Pdesc,String p8_Url, String p9_SAF,String pa_MP,String pd_FrpId,
   String pr_NeedResponse,String keyValue) {
  StringBuilder sValue = new StringBuilder();
  // 业务类型
  sValue.append(p0_Cmd);
  // 商户编号
  sValue.append(p1_MerId);
  // 商户订单号
  sValue.append(p2_Order);
  // 支付金额
  sValue.append(p3_Amt);
  // 交易币种
  sValue.append(p4_Cur);
  // 商品名称
  sValue.append(p5_Pid);
  // 商品种类
  sValue.append(p6_Pcat);
  // 商品描述
  sValue.append(p7_Pdesc);
  // 商户接收支付成功数据的地址
  sValue.append(p8_Url);
  // 送货地址
  sValue.append(p9_SAF);
  // 商户扩展信息
  sValue.append(pa_MP);
  // 银行编码
  sValue.append(pd_FrpId);
  // 应答机制
  sValue.append(pr_NeedResponse);
 
  return PaymentUtil.hmacSign(sValue.toString(), keyValue);
 }
 
 /**
  * 返回校验hmac方法
  *
  * @param hmac 支付网关发来的加密验证码
  * @param p1_MerId 商户编号
  * @param r0_Cmd 业务类型
  * @param r1_Code 支付结果
  * @param r2_TrxId 易宝支付交易流水号
  * @param r3_Amt 支付金额
  * @param r4_Cur 交易币种
  * @param r5_Pid 商品名称
  * @param r6_Order 商户订单号
  * @param r7_Uid 易宝支付会员ID
  * @param r8_MP 商户扩展信息
  * @param r9_BType 交易结果返回类型
  * @param keyValue 密钥
  * @return
  */
 public static boolean verifyCallback(String hmac, String p1_MerId,
   String r0_Cmd, String r1_Code, String r2_TrxId, String r3_Amt,
   String r4_Cur, String r5_Pid, String r6_Order, String r7_Uid,
   String r8_MP, String r9_BType, String keyValue) {
  StringBuilder sValue = new StringBuilder();
  // 商户编号
  sValue.append(p1_MerId);
  // 业务类型
  sValue.append(r0_Cmd);
  // 支付结果
  sValue.append(r1_Code);
  // 易宝支付交易流水号
  sValue.append(r2_TrxId);
  // 支付金额
  sValue.append(r3_Amt);
  // 交易币种
  sValue.append(r4_Cur);
  // 商品名称
  sValue.append(r5_Pid);
  // 商户订单号
  sValue.append(r6_Order);
  // 易宝支付会员ID
  sValue.append(r7_Uid);
  // 商户扩展信息
  sValue.append(r8_MP);
  // 交易结果返回类型
  sValue.append(r9_BType);
  String sNewString = PaymentUtil.hmacSign(sValue.toString(), keyValue);
  return sNewString.equals(hmac);
 }
 
 /**
  * @param aValue
  * @param aKey
  * @return
  */
 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();
 }
 
 /**
  *
  * @param args
  * @param key
  * @return
  */
 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));
 
 }
 
// public static void main(String[] args) {
//  System.out.println(hmacSign("AnnulCard1000043252120080620160450.0http://localhost/SZXpro/callback.asp杩?4564868265473632445648682654736324511","8UPp0KE8sq73zVP370vko7C39403rtK1YwX40Td6irH216036H27Eb12792t"));
// }
}

易宝回调

点对点:易宝直接访问电商,这里没有客户端什么事了

    这种方式是必须要使用的,我们这种方式是收不到的!因为我们没有固定IP
    易宝有一个重发机制,如果它访问你,你不给它回信息,它会一直重发!
    电商需要返回一个以SUCCESS开头的字符串即可!

引导客户端浏览器重定向到电商。是让客户端访问电商!

    可以不使用的!

hmac:13参数值+keyValue(密钥) + 算法(md5)

    13参数值:自己设置的!
    keyValue:易宝在我们注册后发给我们的,这个东东只有我们和易宝知道!
    底层为md5的算法:PaymentUtil.buildHmac(14个),它返回hmac

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。