第三方支付方式:易宝支付。
支付过程图解:
相关资料:
易宝支付产品通用接口帮助文档点击打开链接
使用步骤:
①src下面放入支付测试用的merchantInfo.properties(包含商家id【p1_MerId=10001126856】+加密解密用的keyValue【keyValue=69cl522AV6q613Ii4W6u8K6XuW8vM1N6bFgyv769220IuYe9u37N4y7rI4Pl】+支付成功后的重定向路径)
注意:下面的p8_Url是支付成功后供用户重定向返回网站或者服务器之间点对点通知支付成功用的。所以其ip是内网localhost时,只能通过用户重定向返回网站通知支付成功,易宝支付是无法通知网站支付成功的(除了用公网)。
1
p1_MerId=10001126856
2
keyValue=69cl522AV6q613Ii4W6u8K6XuW8vM1N6bFgyv769220IuYe9u37N4y7rI4Pl
3
p8_Url=http\://localhost\:8080/test/servlet/PaymentResponseServlet
1
<%@page language="java" import="java.util.*" contentType="text/html;charset=utf-8"%>
2
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
3
<html>
4
<head>
5
<title>在线支付</title>
6
</head>
7
8
<body>
9
<form action="${pageContext.request.contextPath }/servlet/PayServlet" method="post">
10
<table width="60%">
11
<tr>
12
<td bgcolor="#F7FEFF" colspan="4">
13
<!-- 实际开发中:这里的订单号和支付金额来自servlet传递过来 -->
14
订单号:<INPUT TYPE="text" NAME="orderId" value="20170815110723">
15
支付金额:<INPUT TYPE="text" NAME="money" size="6" value="0.01">元</td>
16
</tr>
17
<tr>
18
<td>
19
<br/>
20
</td>
21
</tr>
22
<tr>
23
<td>请您选择在线支付银行</td>
24
</tr>
25
<tr>
26
<td><INPUT TYPE="radio" NAME="pd_FrpId" value="ICBC-NET">工商银行</td>
27
<td><INPUT TYPE="radio" NAME="pd_FrpId" value="ABC-NET">农业银行</td>
28
<td><INPUT TYPE="radio" NAME="pd_FrpId" value="CCB-NET">建设银行</td>
29
<td><INPUT TYPE="radio" NAME="pd_FrpId" value="CMBCHINA-NET">招商银行</td>
30
</tr>
31
<tr>
32
<td><INPUT TYPE="radio" NAME="pd_FrpId" value="CMBC-NET">中国民生银行总行</td>
33
<td><INPUT TYPE="radio" NAME="pd_FrpId" value="CEB-NET">光大银行</td>
34
<td><INPUT TYPE="radio" NAME="pd_FrpId" value="BOCO-NET">交通银行</td>
35
<td><INPUT TYPE="radio" NAME="pd_FrpId" value="SDB-NET">深圳发展银行</td>
36
</tr>
37
<tr>
38
<td><INPUT TYPE="radio" NAME="pd_FrpId" value="BCCB-NET">北京银行</td>
39
<td><INPUT TYPE="radio" NAME="pd_FrpId" value="CIB-NET">兴业银行</td>
40
<td><INPUT TYPE="radio" NAME="pd_FrpId" value="SPDB-NET">上海浦东发展银行</td>
41
<td><INPUT TYPE="radio" NAME="pd_FrpId" value="ECITIC-NET">中信银行</td>
42
</tr>
43
<tr>
44
<td><INPUT TYPE="radio" NAME="pd_FrpId" value="POST-NET-B2C">中国邮政</td>
45
<td><INPUT TYPE="radio" NAME="pd_FrpId" value="BOC-NET-B2C">中国银行</td>
46
</tr>
47
<tr>
48
<td>
49
<br/>
50
</td>
51
</tr>
52
<tr>
53
<td><input type="submit" value="进入银行支付" /></td>
54
</tr>
55
</table>
56
</form>
57
</body>
58
</html>
③pay.jsp页面选择支付银行后,进入servlet,组织第三方易宝支付需要的数据,最后重定向到第三方提供的支付链接https://www.yeepay.com/app-merchant-proxy/node 上。
1
package com.cn.pay;
2
3
import java.io.IOException;
4
5
import javax.servlet.ServletException;
6
import javax.servlet.http.HttpServlet;
7
import javax.servlet.http.HttpServletRequest;
8
import javax.servlet.http.HttpServletResponse;
9
10
import com.cn.pay.utils.PaymentUtil;
11
import com.cn.pay.utils.PropertiesUtils;
12
13
/**
14
* 按照第三方接入规范的要求,组织数据
15
* @author liuzhiyong
16
*
17
*/
18
public class PayServlet extends HttpServlet {
19
20
public void doGet(HttpServletRequest request, HttpServletResponse response)
21
throws ServletException, IOException {
22
23
String p0_Cmd = "Buy"; //业务类型 固定值“Buy” .
24
String p1_MerId = PropertiesUtils.getPropertiesByKey("p1_MerId");//商户编号
25
String p2_Order = request.getParameter("orderId");// 商户订单号
26
String p3_Amt = request.getParameter("money");// 支付金额
27
String p4_Cur = "CNY"; // 固定值 ”CNY”.
28
String p5_Pid = "goodName"; // 商品名称
29
String p6_Pcat = ""; // 商品种类
30
String p7_Pdesc = ""; // 商品描述
31
String p8_Url = PropertiesUtils.getPropertiesByKey("p8_Url"); // 商户接收支付成功数据的地址
32
String p9_SAF = "1"; // 送货地址 为“1”: 需要用户将送货地址留在易宝支付系统;为“0”: 不需要,默认为 ”0”.
33
String pa_MP = ""; // 商户扩展信息
34
String pd_FrpId = request.getParameter("pd_FrpId"); // 支付通道编码
35
String pr_NeedResponse = "1"; //固定值为“1”: 需要应答机制; 收到易宝支付服务器点对点支付成功通知,必须回写以”success”(无关大小写)开头的字符串,即使您收到成功通知时发现该订单已经处理过,也要正确回写”success”,否则易宝支付将认为您的系统没有收到通知,启动重发机制,直到收到”success”为止。
36
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, PropertiesUtils.getPropertiesByKey("keyValue")); // 签名数据
37
38
/**
39
* 把组织好的数据重定向到正式请求地址:https://www.yeepay.com/app-merchant-proxy/node
40
商户:把交易信息明文加密--->密文 然后把明文和密文都交给发送给第三方易宝
41
第三方易宝拿到数据后,把传过来的明文和传过来密文比较,
42
如果数据相同,则说明没有被篡改 (易宝与商家加密时都用的商户注册时给的相同key)
43
*/
44
response.sendRedirect("https://www.yeepay.com/app-merchant-proxy/node?" +
45
"p0_Cmd="+p0_Cmd+"&p1_MerId="+p1_MerId+"&p2_Order="+p2_Order+"&p3_Amt="+p3_Amt+"&p4_Cur="+p4_Cur+
46
"&p5_Pid="+p5_Pid+"&p6_Pcat="+p6_Pcat+"&p7_Pdesc="+p7_Pdesc+"&p8_Url="+p8_Url+"&p9_SAF="+p9_SAF+
47
"&pa_MP="+pa_MP+"&pd_FrpId="+pd_FrpId+"&pr_NeedResponse="+pr_NeedResponse+"&hmac="+hmac);
48
}
49
50
public void doPost(HttpServletRequest request, HttpServletResponse response)
51
throws ServletException, IOException {
52
53
this.doGet(request, response);
54
}
55
56
}
读取properties文件时用到的自定义工具PropertiesUtils.java
1
package com.cn.pay.utils;
2
3
import java.util.ResourceBundle;
4
5
public class PropertiesUtils {
6
7
private static ResourceBundle bundle;
8
9
//加载一次配置文件
10
static{
11
bundle = ResourceBundle.getBundle("merchantInfo");
12
}
13
14
public static String getPropertiesByKey(String key){
15
16
return bundle.getString(key);
17
}
18
}
19
③只有当用户付款成功后,第三方易宝支付会通知用户(浏览器重定向)和商户(服务器点对点通讯)。这时,系统再进行付款成功后的业务逻辑处理(重定向的url在merchantInfo.properties配置中)。
1
package com.cn.pay;
2
3
import java.io.IOException;
4
import java.io.PrintWriter;
5
6
import javax.servlet.ServletException;
7
import javax.servlet.http.HttpServlet;
8
import javax.servlet.http.HttpServletRequest;
9
import javax.servlet.http.HttpServletResponse;
10
11
import com.cn.pay.utils.PaymentUtil;
12
import com.cn.pay.utils.PropertiesUtils;
13
14
/**
15
* 接受第三方的支付结果:处理自己的订单
16
* @author liuzhiyong
17
*
18
*/
19
public class PaymentResponseServlet extends HttpServlet {
20
21
public void doGet(HttpServletRequest request, HttpServletResponse response)
22
throws ServletException, IOException {
23
24
response.setContentType("text/html;charset=utf-8");
25
PrintWriter out = response.getWriter();
26
27
String p1_MerId = request.getParameter("p1_MerId");//商户编号
28
String r0_Cmd = request.getParameter("r0_Cmd");// 业务类型 返回固定值 "Buy"
29
String r1_Code = request.getParameter("r1_Code");// 支付结果 "1"代表成功
30
String r2_TrxId = request.getParameter("r2_TrxId");// 易宝支付交易流水号
31
String r3_Amt = request.getParameter("r3_Amt");// 支付金额
32
String r4_Cur = request.getParameter("r4_Cur");// 交易币种 返回时是"RMB"
33
String r5_Pid = request.getParameter("r5_Pid");// 商品名称
34
String r6_Order = request.getParameter("r6_Order");// 商户订单号
35
String r7_Uid = request.getParameter("r7_Uid");// 易宝支付会员ID
36
String r8_MP = request.getParameter("r8_MP");// 商户扩展信息
37
String r9_BType = request.getParameter("r9_BType");// 交易结果返回类型 为“1”: 浏览器重定向; 为“2”: 服务器点对点通讯.
38
String hmac = request.getParameter("hmac");//签名数据
39
40
String rp_PayDate = request.getParameter("r9_BType");// 支付成功时间 该返回参数不参与到hmac校验,范例中没有收录,可根据您的需要自行添加.
41
42
/**
43
* 数据校验是否正确
44
易宝第三方:把交易信息明文加密--->密文 然后把明文和密文都交给发送给商户
45
商户拿到数据后,把传过来的明文和传过来密文比较,
46
如果数据相同,则说明没有被篡改 (商家与易宝加密时都用的商户注册时给的相同key)
47
*/
48
boolean flag = PaymentUtil.verifyCallback(hmac, p1_MerId, r0_Cmd, r1_Code, r2_TrxId, r3_Amt, r4_Cur, r5_Pid, r6_Order, r7_Uid, r8_MP, r9_BType, PropertiesUtils.getPropertiesByKey("keyValue"));
49
50
if(!flag){//数据不一致
51
out.write("交易失败!可能存在风险!");
52
return;//结束
53
}
54
55
if("1".equals(r1_Code)){//说明支付成功
56
//支付成功:找到订单,更改订单的支付状态。
57
/**
58
* 只有支付成功时易宝支付才会通知商户。支付成功回调有两种,都会以GET形式通知到在线支付请求参数中的p8_Url上:■ 浏览器重定向■ 服务器点对点通讯
59
关于两种通知和业务处理说明:如果用户在支付成功后,并没有通知商家而是直接关闭了重定向的窗口,那么重定向就不会通知到商户,不管用户是否重定向通知到商户,
60
服务器点对点通知都会通知到商户,所以在callback页中r9_btype=1和r9_btype=2的两种通知类型中都要进行业务处理。
61
并注意处理重复订单的问题,以防两次通知都处理了相同的业务造成损失。
62
应答机制是指当贵公司系统收到易宝支付的支付成功数据通知(服务器点对点通讯形式)时,必须回写以“success”开头的stream,易宝支付收到该stream,便认为贵公司已收到;否则将继续发送通知,直至收到。
63
*/
64
if("1".equals(r9_BType)){//服务器点对点通讯.
65
System.out.println("#################交易成功");
66
out.write("<h2>订单号:<span style='color:red;'>"+r6_Order+"</span>,合计:<span style='color:red;'>"+r3_Amt+"</span>元,交易成功!<h2>");
67
/**
68
* 以下是处理交易成功后的业务逻辑,注意处理重复订单的问题,以防两次通知都处理了相同的业务造成损失。
69
*/
70
}
71
if("2".equals(r9_BType)){//服务器点对点通讯.
72
out.write("success");//回写以“success”开头的stream,易宝支付收到该stream,便认为贵公司已收到;否则将继续发送通知,直至收到。
73
/**
74
* 以下是处理交易成功后的业务逻辑,注意处理重复订单的问题,以防两次通知都处理了相同的业务造成损失。
75
*/
76
}
77
}
78
}
79
80
public void doPost(HttpServletRequest request, HttpServletResponse response)
81
throws ServletException, IOException {
82
83
this.doGet(request, response);
84
}
85
86
}
第三方易宝支付自己的支付工具(加密+验证)PaymentUtil.java
1
package com.cn.utils;
2
3
4
import java.io.UnsupportedEncodingException;
5
import java.security.MessageDigest;
6
import java.security.NoSuchAlgorithmException;
7
import java.util.Arrays;
8
9
public class PaymentUtil {
10
11
private static String encodingCharset = "UTF-8";
12
13
/**
14
* 生成hmac方法
15
*
16
* @param p0_Cmd 业务类型
17
* @param p1_MerId 商户编号
18
* @param p2_Order 商户订单号
19
* @param p3_Amt 支付金额
20
* @param p4_Cur 交易币种
21
* @param p5_Pid 商品名称
22
* @param p6_Pcat 商品种类
23
* @param p7_Pdesc 商品描述
24
* @param p8_Url 商户接收支付成功数据的地址
25
* @param p9_SAF 送货地址
26
* @param pa_MP 商户扩展信息
27
* @param pd_FrpId 银行编码
28
* @param pr_NeedResponse 应答机制
29
* @param keyValue 商户**
30
* @return
31
*/
32
public static String buildHmac(String p0_Cmd,String p1_MerId,
33
String p2_Order, String p3_Amt, String p4_Cur,String p5_Pid, String p6_Pcat,
34
String p7_Pdesc,String p8_Url, String p9_SAF,String pa_MP,String pd_FrpId,
35
String pr_NeedResponse,String keyValue) {
36
StringBuilder sValue = new StringBuilder();
37
// 业务类型
38
sValue.append(p0_Cmd);
39
// 商户编号
40
sValue.append(p1_MerId);
41
// 商户订单号
42
sValue.append(p2_Order);
43
// 支付金额
44
sValue.append(p3_Amt);
45
// 交易币种
46
sValue.append(p4_Cur);
47
// 商品名称
48
sValue.append(p5_Pid);
49
// 商品种类
50
sValue.append(p6_Pcat);
51
// 商品描述
52
sValue.append(p7_Pdesc);
53
// 商户接收支付成功数据的地址
54
sValue.append(p8_Url);
55
// 送货地址
56
sValue.append(p9_SAF);
57
// 商户扩展信息
58
sValue.append(pa_MP);
59
// 银行编码
60
sValue.append(pd_FrpId);
61
// 应答机制
62
sValue.append(pr_NeedResponse);
63
64
return PaymentUtil.hmacSign(sValue.toString(), keyValue);
65
}
66
67
/**
68
* 返回校验hmac方法
69
*
70
* @param hmac 支付网关发来的加密验证码
71
* @param p1_MerId 商户编号
72
* @param r0_Cmd 业务类型
73
* @param r1_Code 支付结果
74
* @param r2_TrxId 易宝支付交易流水号
75
* @param r3_Amt 支付金额
76
* @param r4_Cur 交易币种
77
* @param r5_Pid 商品名称
78
* @param r6_Order 商户订单号
79
* @param r7_Uid 易宝支付会员ID
80
* @param r8_MP 商户扩展信息
81
* @param r9_BType 交易结果返回类型
82
* @param keyValue **
83
* @return
84
*/
85
public static boolean verifyCallback(String hmac, String p1_MerId,
86
String r0_Cmd, String r1_Code, String r2_TrxId, String r3_Amt,
87
String r4_Cur, String r5_Pid, String r6_Order, String r7_Uid,
88
String r8_MP, String r9_BType, String keyValue) {
89
StringBuilder sValue = new StringBuilder();
90
// 商户编号
91
sValue.append(p1_MerId);
92
// 业务类型
93
sValue.append(r0_Cmd);
94
// 支付结果
95
sValue.append(r1_Code);
96
// 易宝支付交易流水号
97
sValue.append(r2_TrxId);
98
// 支付金额
99
sValue.append(r3_Amt);
100
// 交易币种
101
sValue.append(r4_Cur);
102
// 商品名称
103
sValue.append(r5_Pid);
104
// 商户订单号
105
sValue.append(r6_Order);
106
// 易宝支付会员ID
107
sValue.append(r7_Uid);
108
// 商户扩展信息
109
sValue.append(r8_MP);
110
// 交易结果返回类型
111
sValue.append(r9_BType);
112
String sNewString = PaymentUtil.hmacSign(sValue.toString(), keyValue);
113
return sNewString.equals(hmac);
114
}
115
116
/**
117
* @param aValue
118
* @param aKey
119
* @return
120
*/
121
public static String hmacSign(String aValue, String aKey) {
122
byte k_ipad[] = new byte[64];
123
byte k_opad[] = new byte[64];
124
byte keyb[];
125
byte value[];
126
try {
127
keyb = aKey.getBytes(encodingCharset);
128
value = aValue.getBytes(encodingCharset);
129
} catch (UnsupportedEncodingException e) {
130
keyb = aKey.getBytes();
131
value = aValue.getBytes();
132
}
133
134
Arrays.fill(k_ipad, keyb.length, 64, (byte) 54);
135
Arrays.fill(k_opad, keyb.length, 64, (byte) 92);
136
for (int i = 0; i < keyb.length; i++) {
137
k_ipad[i] = (byte) (keyb[i] ^ 0x36);
138
k_opad[i] = (byte) (keyb[i] ^ 0x5c);
139
}
140
141
MessageDigest md = null;
142
try {
143
md = MessageDigest.getInstance("MD5");
144
} catch (NoSuchAlgorithmException e) {
145
146
return null;
147
}
148
md.update(k_ipad);
149
md.update(value);
150
byte dg[] = md.digest();
151
md.reset();
152
md.update(k_opad);
153
md.update(dg, 0, 16);
154
dg = md.digest();
155
return toHex(dg);
156
}
157
158
public static String toHex(byte input[]) {
159
if (input == null)
160
return null;
161
StringBuffer output = new StringBuffer(input.length * 2);
162
for (int i = 0; i < input.length; i++) {
163
int current = input[i] & 0xff;
164
if (current < 16)
165
output.append("0");
166
output.append(Integer.toString(current, 16));
167
}
168
169
return output.toString();
170
}
171
172
/**
173
*
174
* @param args
175
* @param key
176
* @return
177
*/
178
public static String getHmac(String[] args, String key) {
179
if (args == null || args.length == 0) {
180
return (null);
181
}
182
StringBuffer str = new StringBuffer();
183
for (int i = 0; i < args.length; i++) {
184
str.append(args[i]);
185
}
186
return (hmacSign(str.toString(), key));
187
}
188
189
/**
190
* @param aValue
191
* @return
192
*/
193
public static String digest(String aValue) {
194
aValue = aValue.trim();
195
byte value[];
196
try {
197
value = aValue.getBytes(encodingCharset);
198
} catch (UnsupportedEncodingException e) {
199
value = aValue.getBytes();
200
}
201
MessageDigest md = null;
202
try {
203
md = MessageDigest.getInstance("SHA");
204
} catch (NoSuchAlgorithmException e) {
205
e.printStackTrace();
206
return null;
207
}
208
return toHex(md.digest(value));
209
210
}
211
}
支付演示
pay.jsp支付页面,选择支付银行
进入相应银行网站
输入相应短信验证(不同银行过程不同)
银行引导用户访问第三方易宝支付,携带相应支付完成信息,跳转到电商网站,提示信息(完成支付成功后的逻辑业务)