项目环境概述 工具集下载
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