项目环境概述 工具集下载
Eclipse:
版本: eclipse-mars
下载地址: https://pan.baidu.com/s/1ci4Nim
Tomcat:
版本: apache-tomcat-7.0.59
下载地址: https://pan.baidu.com/s/1eS6JlrO
Maven:
版本: apache-maven-3.0.2
下载地址: https://pan.baidu.com/s/1i4Ud35F
JDK:
版本: JDK1.7
下载地址: https://pan.baidu.com/s/1jHWJSdK
1. 项目准备
项目运行前准备: 本项目为maven工程, 工程所有依赖的jar包需要根据pom.xml进行下载
没有使用过maven工程的小伙伴请阅读下 【eclipse如何导入和配置maven工程】,看完之后,你就可以将项目跑起来了,
本项目分为二个工程,一个是 ZhiFuBaoClient(商家A),另一个是 ZhiFuBaoServer(支付宝服务B)
商家A与支付宝B的交互需要使用到加密算法RSA, 如果不了解RSA, 请参照 RSA实例详解
商家A 需要配备 RSA加密算法的 私钥与公钥, 支付宝服务B 需要配备RSA加密算法的 私钥与公钥,
同时将A的公钥交给B, 将B的公钥交给A, 这样A,B两端就互相拥有对方的公钥
A拥有的密钥: A自己的公钥,私钥,B的公钥(对B的数据进行验签, 验签通过,则表明数据未被篡改)
B拥有的密钥: B自己的公钥,私钥,A的公钥(对A的数据进行验签,验签通过,则表明数据未被篡改);
由于A需要使用HttpUtils.sendPost将数据发送给B,在A中进行B接口路径的配置,如下图:

本demo实现类似 支付的功能, A将购买的产品数据进行组装加密 发送给 B, B进行数据校验,如果数据正确
则将支付结果信息返回给A

2. 项目运行
2.1 启动 项目 ZhiFuBaoClient 和 ZhiFuBaoServer
启动完成后,ZhiFuBaoClient是有页面的, 在浏览器中输入 http://localhost:8080/ZhiFuBaoClient/pay/getPayPage.htm, 见下图

2.2 输入产品编号,产品名称,购买数量, 产品总价后(四个编辑框必填,使用BootstrapValid进行校验)
点击【支付】, 如下 图B 所示,则支付成功说明项目运行正常

图 A

图B
3. 项目详解
3.1 客户端前台代码, 代码实现图B的效果,点击【支付】通过ajax方式向后台发送数据
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%String contextPath = request.getContextPath();%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试页面</title>
<link rel="stylesheet" href="<%=contextPath%>/jsLib/bootstrap3/css/bootstrap.min.css">
</head>
<body>
<!-- 添加商品 -->
<div id="myModal">
<div>
<div>
<form id="goodsInfoForm" role="form" style="margin-left: 30px; margin:30px;">
<div>
<label for="goodsNo" class="col-sm-3 control-label"><span>*</span>产品编号:</label>
<div>
<input ng-model="goods.goodsNo" type="text" id="goodsNo" name="goodsNo"
value='' placeholder="请输入产品编号(必填)"/>
</div>
</div>
<div>
<label for="goodsName" class="col-sm-3 control-label"><span>*</span>产品名称:</label>
<div>
<input ng-model="goods.goodsName" type="text" id="goodsName" name="goodsName"
value='' placeholder="请输入产品名称(必填)">
</div>
</div>
<div>
<label for="goodsNum" class="col-sm-3 control-label"><span>*</span>购买数量:</label>
<div>
<input ng-model="goods.goodsNum" type="text" id="goodsNum" name="goodsNum"
value='' placeholder="请输入购买数量(必填)">
</div>
</div>
<div>
<label for="goodsMoney" class="col-sm-3 control-label"><span>*</span>产品总价:</label>
<div>
<input ng-model="goods.goodsPrice" type="text" id="goodsMoney" name="goodsMoney"
value='' placeholder="请输入产品总价(总价)">
</div>
</div>
<div style="text-align:center;">
<button type="button" class="btn btn-primary" id="pay">支付<tton>
<button type="button" class="btn btn-default closeModel">关闭<tton>
</div>
</form>
</div>
</div>
</div>
</body>
<script type="text/javascript" src="<%=contextPath%>/jsLib/jquery/jquery.js"></script>
<script type="text/javascript" src="<%=contextPath%>/jsLib/bootstrap3/js/bootstrap.min.js"></script>
<script type="text/javascript" src="<%=contextPath %>/jsLib/bootstrapValid/bootstrapValidator.js"></script>
<script>
$(function () {
$('#pay').click(function(){
var bSuccess = $('#goodsInfoForm').data("bootstrapValidator").isValid();
if(bSuccess){
var goodsData = $('#goodsInfoForm').serialize();
$.ajax({
url: '<%=contextPath%>/pay/payOrder.htm',
type: 'post',
data: goodsData,
success: function (data, status) {
data = JSON.parse(data);
if(data.stateCode == 0){
alert(data.desc);
}else{
alert(data.desc);
}
},
fail: function (err, status) {
console.log(err)
}
});
}else{
alert('信息填写不完整,不能支付!');
}
});
//校验form表单
//校验bootstrap form表单
$('#goodsInfoForm')
.bootstrapValidator({
message: 'This value is not valid',
feedbackIcons: {
valid: 'glyphicon glyphicon-ok',
invalid: 'glyphicon glyphicon-remove',
validating: 'glyphicon glyphicon-refresh'
},
fields: {
'goodsNo': {
validators: {
notEmpty: {
message: '产品编号不能为空'
},
stringLength: {
min: 4,
message: '产品编号最少4位'
},
numeric: {
message: '税额只能输入数字'
}
}
},
'goodsName': {
validators: {
notEmpty: {
message: '产品名称不能为空'
}
}
},
'goodsNum': {
validators: {
notEmpty: {
message: '购买数量不能为空'
},
numeric: {
message: '税额只能输入数字'
}
}
},
'goodsMoney': {
validators: {
notEmpty: {
message: '产品总价不能为空'
},
numeric: {
message: '税额只能输入数字'
}
}
}
}
}).on('success.form.bv', function(e) {
alert('aaaaa');
});
});
</script>
</html>3.2 客户端后台代码, 将前台的数据使用 商户RSA密钥进行加密, 将加密信息调用 HttpUtils.sendPost给支付宝,
支付宝返回结果后,客户端使用 RSAUtils.verify校验响应结果, 将结果返回给页面
/**
* 支付订单
* @param map
* @param request
* @return
*/
@RequestMapping("/payOrder.htm")
@ResponseBody
public Result payOrder(PayOrderRequestDTO payOrderRequest, ModelMap map, HttpServletRequest request) throws Exception
{
Result result = new Result();
PayCommonDTO payCommonDto = PayUtil.getPayCommon();
String timeStamp = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss:SSS");
payCommonDto.setTimeStamp(timeStamp);
payCommonDto.setService("payOrder");
PayOrderDTO payOrderDto = new PayOrderDTO();
BeanUtils.copyProperties(payCommonDto, payOrderDto);
payOrderDto.setReqData(payOrderRequest);
PayUtil.addSign(payOrderDto);
String strSendContent = PayUtil.convertToXml(payOrderDto, "message");
System.out.println("请求消息:" + strSendContent);
String strRecvContent = HttpUtils.sendPost(payOrderDto.getUrl(), strSendContent);
System.out.println("接收消息:" + strRecvContent);
PayOrderDTO payOrderRes = null;
String resData = StringUtil.getXmlTagValue(strRecvContent, "resData");
payOrderRes = PayUtil.converyToJavaBean(strRecvContent);
String signcoent = payOrderRes.getResCode() + resData + payOrderRes.getResMsg();
//临时把验签去掉
boolean bVerify = RSAUtils.verify(signcoent, payOrderDto.getZhiFuBaoPublicKey(),
payOrderRes.getSign());
if(bVerify){
System.out.println("验签成功!!");
}else{
System.out.println("验签不成功!!");
}
if(payOrderRes.getResCode().equals("success")){
result.setStateCode("0");
result.setDesc(payOrderRes.getResMsg());
}else if (payOrderRes.getResCode().equals("false")){
result.setStateCode("-1");
result.setDesc(payOrderRes.getResMsg());
}
System.out.println(payOrderRes.getResMsg());
return result;
}3.3 模拟支付宝的后台代码
支付宝后台获取客户端数据后,根据与客户端约定的规则,获取需要加密的数据和加密的密文,使用商户公钥检验数据是否被篡改,如果数据完整,将支付结果返回给客户端, 至此 客户端与支付宝的交易流程结束
//支付宝服务端代码 --支付
@RequestMapping("/payOrder.htm")
public void payOrder(ModelMap map, HttpServletRequest request, HttpServletResponse reponse) throws Exception
{
String strRecvContent = HttpUtils.getRequestData(request);
System.out.println("接收消息:" + strRecvContent);
PayOrderDTO payOrderReq = null;
PayOrderDTO payOrderRes = new PayOrderDTO();
PayCommonDTO payCommonDto = PayUtil.getPayCommon();
String timeStamp = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss:SSS");
payCommonDto.setTimeStamp(timeStamp);
payCommonDto.setService("payOrder");
BeanUtils.copyProperties(payCommonDto, payOrderRes);
String reqData = StringUtil.getXmlTagValue(strRecvContent, "reqData");
payOrderReq = PayUtil.converyToJavaBean(strRecvContent);
PayOrderResponseDTO payOrderResponse = new PayOrderResponseDTO();
payOrderResponse.setTradeNo(payOrderReq.getReqData().getTradeNo());
//支付订单
if (payOrderReq.getService().equals("payOrder")){
reqData = payOrderReq.getAppkey() + payOrderReq.getCharset() + reqData
+ payOrderReq.getService()
+ payOrderReq.getSignType() + payOrderReq.getTimeStamp();
//使用 商家公钥 对商家数据进行验证
boolean bVerify = RSAUtils.verify(reqData, PayUtil.getPayCommon().getMerchantPublicKey(),
payOrderReq.getSign());
if(bVerify){
payOrderResponse.setOutTradeNo("33320183793273203972379203082373");
payOrderRes.setResCode("success");
payOrderRes.setResMsg("支付成功");
}else{
payOrderRes.setResCode("false");
payOrderRes.setResMsg("数据验签不通过!");
}
}else{
payOrderRes.setResCode("false");
payOrderRes.setResMsg("调用接口不正确");
}
payOrderRes.setResData(payOrderResponse);
PayUtil.addSign(payOrderRes);
String strSendContent = PayUtil.convertToXml(payOrderRes, "message");
HttpUtils.writeRespToPage(strSendContent, reponse);
System.out.println("发送消息:" + strSendContent);
return;
}4. 数据报文
支付交易过程中,客户端与服务端的数据都有相应的数据格式, 使用私钥加密 与 公钥验密 都需要根据这些数据进行,下面详细介绍一下数据格式 与 加密验密 (数据以XML格式进行传输)
4.1 下面的为客户端的报文数据,加密前<sign>为空
<?xml version="1.0" encoding="utf-8"?> <message> <service>payOrder</service> <appkey>azzfefg84d5d33fd7fd3d26d433</appkey> <charset>UTF-8</charset> <sign>kbP+CGHmNodKp+Vo+JG62EnETPMq90CqzGlI5bUErU6FarunEnJPAXOa3BMrU7gH4KvQXpT8B0YFw81A8YN7hL0v/fpPaufuyzny+tB+JKadUw+WIieOBfb6nQuMaY/uGvKa/i1/AFwTwaUOlILyCA3b7M7KbK/zERc3sxbZXTo=</sign> <signType>RSA</signType> <timeStamp>2017-12-17 14:18:01:091</timeStamp> <reqData> <goodsNo>2332323</goodsNo> <goodsName>IPhone8</goodsName> <goodsNum>2</goodsNum> <goodsMoney>18899</goodsMoney> </reqData> </message>
加密后<sign>为如上图所示
加密字段为: appKey + 编码格式(UTF-8) + 请求数据体 + serviceName + 加密方式(RSA) + 时间戳, 数据如下:
azzfefg84d5d33fd7fd3d26d433UTF-8<reqData><goodsNo>2332323</goodsNo><goodsName>IPhone8</goodsName><goodsNum>2</goodsNum><goodsMoney>18899</goodsMoney></reqData>payOrderRSA2017-12-17 15:30:21:468
将上面数据使用商户私钥进行加密
商户私钥:
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKfeAsDG9JYp0hU5K7Q8XGcwhV1ybuRmRNQL7QYXo6QigzjOPLrV6fVVYx/pbj8wxBIbFzyqZLQiEHm/pixVPucbeK1jke/R26EpENV4kRy3JagbdsqVQv9DPweYcSAmiL7/FAl0WCfEJkXkCagYm1CEtSEJ4ilBHxXp2kKwSqQ7AgMBAAECgYEAnYQ9oRcPuzhS4Ydb8ywQqONmwWD3nWo5e6AVMXpNG18nMs6TPd4sQwF0miU4RiNEWJkDHPHmvQCZ5SRokYEMG9q8rYbajrTzCVnCLWUwQL3YDwUaJchUsH8cREMwvvpoqratgIMEGy89E2MPtRO+ZVWZITHnDa0siHMMUxV+/cECQQDZI+QPrlbDd+gegnql4xm1fxRkX2gxWaGQJZH3IvnCAzHJpFDtU5ulyzbeMxB+5n26g37PfPsMEaU5jAFd6IfHAkEAxei3mIaxVgzgL4H4Nyoj+/xg6BvEsLs0Y1ovurpXond59lwTTyZB59ZvbzIswAYLfPh9phiV3J+3fhklCj2H7QJABz1MGD2+xMuVoJbHEgrNS6DOBD6uEZ8kZNLr1+qBmzdSDJ/+1rrH4LIyxRu8vA5hOLuzmaVYFWHtOUryrLfY9wJAOpvAQxsgSStm+Kq0pyGDpowG5rXSecP2r7V1jQbCDQr0w1BhJ39c5RtLxNJHDla78DZmf1moh72EyYMIxQ+TwQJBAIxW98my6iLXdZrNntIBNTEHp9l9Cr5EKYU0dlbBm+uiDbmukK5STkVw8BGW9rpanWS5xNgeHnK36zJmEz16KM4=
4.2 客户端验密
验密前数据:
<?xml version="1.0" encoding="utf-8"?> <message> <service>payOrder</service> <appkey>azzfefg84d5d33fd7fd3d26d433</appkey> <charset>UTF-8</charset> <sign>eJjw7OUpj9FfPdqG1htV+1zc32DI79IcoCCU1TD2j7SsuG7UJz5sc3kn5D6T5GgyGeYnu5cV9e21Xbgxg03E86+HJryWHyoA9QzGwhRGKbd+a5+PEToHJvWchVzzY0C1FrLq5JU+KsPIH+POsYB7nE+L4z15RaHr1uTx4FZC1bk=</sign> <signType>RSA</signType> <timeStamp>2017-12-17 15:36:31:256</timeStamp> <resCode>success</resCode> <resMsg>支付成功</resMsg> <resData> <outTradeNo>33320183793273203972379203082373</outTradeNo> </resData> </message>
使用支付宝公钥进行验密:
验密数据: 结果标志(true, false) + 服务端响应数据 + 结果描述
success<resData><outTradeNo>33320183793273203972379203082373</outTradeNo></resData>支付成功
支付宝公钥:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCPKkTQG8oCHhcu5ubKk5oGsvp9OpzTbgkGH/Ls6v5KRZcBNGb+RTWFsAkllP+CCG7OhvqPrwaWIolBr6Eymi3ISzezkVSnqqMurH6gjf90zaPHEcpYzdjSlXcpLE+lCCf121q7QqpxkEoJ6QIgMeynRKFyw8CwF43R4NSsyJUyxQIDAQAB
4.3 RSA密钥,公钥加密工具(1024位 与 2048位 两种)
下载地址: https://pan.baidu.com/s/1ge84MgN

具体可参见代码, 如果有疑问,请加q: 549710689
下载Demo