shop第七天
学习目标
支付宝支付沙箱环境集成配置
使用蚂蚁金服开放平台官网提供的支付宝支付api实现在线支付操作,具体集成配置如下(这里以沙箱环境为主实现支付宝在线支付集成操作):
开放平台提供支付接入api 与文档步骤说明,我们先登录支付宝帐号。
开发接入
选择顶部菜单的开发接入,点击进入开发接入页面
可以看到支付宝提供支付应用接入功能,在接入支付功能时,真实账号需要进行实名认证同时需要提供企业工商注册信息 后续经过审核通过后方可使用,手续比较麻烦。这里以沙箱环境为主,重点介绍支付宝支付沙箱环境配置与支付流程分析!!!
继续下拉页面找到开发服板块,然后点击沙箱进入沙箱环境使用说明页面
页面具体介绍了沙箱环境下环境具体配置与环境集成,核心步骤总结如下(详情参看: https://docs.open.alipay.com/200/105311)
入驻商户
按支付宝的提示点击沙箱环境,进入沙箱环境页面,如果你是第一次使用蚂蚁金服开放平台,那么会进入入驻页面,需要加入蚂蚁金服开放平台,成为一名开发者。
按要求填写必填项,然后同意开放平台服务协议,点击提交入驻申请,成为开发者
沙箱应用
配置讲解
返回第三步的刚开始,继续点击沙箱环境,下面会进入沙箱应用页面进行沙箱环境配置
进入页面后会看到如下信息,我们要做的就是设置应用公钥,先来讲解一下相关知识
应用唯一标识id-APPID
在对接支付宝支付时,必须在开放平台配置应用,系统分配唯一的应用标识id 沙箱环境以自动为开发者配置完毕,可以直接使用(生产环境这里需要开发人员手动配置并审核通过方可使用,应用配置地址,参考:
登录支付宝账号后访问: https://openhome.alipay.com/platform/appManage.htm#/apps
支付宝网关
通过网站进行支付时沙箱环境与生产环境的支付网关不同
应用开发者私钥与公钥
在每个应用下集成支付时,开放平台均会提供不同的应用公钥与私钥,每个应用在配置公钥与使用时保证配对正确才能正确完成支付接口调用操作,重点说明公钥与私钥生成,公钥修改,公钥私钥正确性匹配校验
生成RSA密钥
下载密钥生成工具
鼠标移入感叹号,点击密钥的生成方法,进入密钥生成页面
这里以windows系统为例下载相关文件,在本地生成公钥与私钥
点击下载
生成密钥
根据页面详细步骤提示生成密钥,妥善保管
生成的结果会存储在RSA密钥文件夹内
上传公钥
点击设置应用公钥
点击设置应用公钥
验证公钥
修改公钥
如果不小心输入错误,或者以后要修改公钥,点击查看应用公钥
点击修改按钮进行修改
支付SKD集成
下载方式一
在沙箱应用页面,下拉页面可以看到
进入电脑网站支付产品页面,该页面介绍了如何使用SDK以及DEMO代码
点击获取产品
无需开通服务,直接下载DEMO即可
这个DEMO是官方提供的一份完整版的DEMO,资料比较齐全
下载方式二
点击页面左侧导航的SDK&Demo,在右侧点击下载和使用教程可以查看Maven坐标
https://search.maven.org/search?q=g:com.alipay.sdk%20AND%20a:alipay-sdk-java&core=gav
返回页面获取DEMO
这个DEMO内容比较单一(支付相关的),使用此DEMO开发即可
构建支付DEMO项目并测试支付(沙箱环境)
根据DEMO项目中的lib文件夹jar包可以得知我们需要
依赖alipay-sdk坐标和commons-logging坐标
创建项目
添加依赖
pom.xml
<?xmlversion="1.0" encoding="UTF-8"?>
<projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xxxx</groupId>
<artifactId>demo-alipay</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!-- alipay依赖 -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.8.73.ALL</version>
</dependency>
</dependencies>
<build>
<finalName>demo-alipay</finalName>
<plugins>
<!-- 添加maven jetty插件 -->
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<version>6.1.26</version>
<configuration>
<scanIntervalSeconds>10</scanIntervalSeconds>
<!-- 项目访问路径 -->
<contextPath>/demo-alipay</contextPath>
</configuration>
</plugin>
</plugins>
</build>
</project>
复制源码、资源页面到相关目录
修改支付配置类
修改app_id
商户私钥merchant_private_key
支付宝公钥alipay_public_key
支付宝网关gatewayUrl为沙箱环境配置
设置支付同步与异步通知回调地址
AlipayConfig.java
packagecom.alipay.config;
importjava.io.FileWriter;
importjava.io.IOException;
/* *
*类名:AlipayConfig
*功能:基础配置类
*详细:设置帐户有关信息及返回路径
*说明:
*以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。
*该代码仅供学习和研究支付宝接口使用,只是提供一个参考。
*/
publicclassAlipayConfig{
//↓↓↓↓↓↓↓↓↓↓请在这里配置您的基本信息↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
publicstaticStringapp_id="";
// 商户私钥,您的PKCS8格式RSA2私钥
publicstaticStringmerchant_private_key="";
// 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
publicstaticStringalipay_public_key="";
// 服务器异步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
publicstaticStringnotify_url="http://alipay.trade.page.pay-JAVA-UTF-8/notify_url.jsp";
// 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
publicstaticStringreturn_url="http://alipay.trade.page.pay-JAVA-UTF-8/return_url.jsp";
// 签名方式
publicstaticStringsign_type="RSA2";
// 字符编码格式
publicstaticStringcharset="utf-8";
// 支付宝网关
publicstaticStringgatewayUrl="https://openapi.alipaydev.com/gateway.do";
// 支付宝网关
publicstaticStringlog_path="C:\\";
//↑↑↑↑↑↑↑↑↑↑请在这里配置您的基本信息↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
/**
* 写日志,方便测试(看网站需求,也可以改成把记录存入数据库)
* @param sWord 要写入日志里的文本内容
*/
publicstaticvoidlogResult(StringsWord) {
FileWriterwriter=null;
try{
writer=newFileWriter(log_path+"alipay_log_"+System.currentTimeMillis()+".txt");
writer.write(sWord);
}catch(Exceptione) {
e.printStackTrace();
}finally{
if(writer!=null) {
try{
writer.close();
}catch(IOExceptione) {
e.printStackTrace();
}
}
}
}
}
配置jetty启动
访问测试
按步骤进行测试,这里的款项扣除的都是沙箱环境的金额,不是真正的人名币不用担心点击付款
点击登录账户付款
注:手机端执行支付,需要使用沙箱环境支付宝app(目前仅限安卓端测试,ios 端暂没有提供)
回到沙箱帐号页面,使用买家信息登录进行付款
注:支付宝的支付核心业务逻辑写在了jsp页面当中,接下来我们开始详细分析
商城支付功能实现
订单系统添加依赖
shop-parent的pom.xml
<!-- alipay 依赖 -->
<alipay.version>4.8.73.ALL</alipay.version>
<!-- alipay依赖 -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>${alipay.version}</version>
</dependency>
shop-order的pom.xml
<!-- alipay依赖 -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
</dependency>
订单系统添加支付宝配置类
修改支付宝的配置类并且引入订单系统,在原来修改的基础上再添加同步通知地址
shop-order的AlipayConfig.java
packagecom.xxxx.order.config;
importjava.io.FileWriter;
importjava.io.IOException;
/* *
*类名:AlipayConfig
*功能:基础配置类
*详细:设置帐户有关信息及返回路径
*说明:
*以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。
*该代码仅供学习和研究支付宝接口使用,只是提供一个参考。
*/
publicclassAlipayConfig{
//↓↓↓↓↓↓↓↓↓↓请在这里配置您的基本信息↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
publicstaticStringapp_id="2016092800614468";
// 商户私钥,您的PKCS8格式RSA2私钥
publicstaticStringmerchant_private_key="";
// 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
publicstaticStringalipay_public_key="";
// 服务器异步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
publicstaticStringnotify_url="http://工程公网访问地址/alipay.trade.page.pay-JAVA-UTF-8/notify_url.jsp";
// 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
publicstaticStringreturn_url="http://localhost:9092/shop-order/order/myOrder";
// 签名方式
publicstaticStringsign_type="RSA2";
// 字符编码格式
publicstaticStringcharset="utf-8";
// 支付宝网关
publicstaticStringgatewayUrl="https://openapi.alipaydev.com/gateway.do";
// 支付宝网关
publicstaticStringlog_path="C:\\";
//↑↑↑↑↑↑↑↑↑↑请在这里配置您的基本信息↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
/**
* 写日志,方便测试(看网站需求,也可以改成把记录存入数据库)
*
* @param sWord 要写入日志里的文本内容
*/
publicstaticvoidlogResult(StringsWord) {
FileWriterwriter=null;
try{
writer=newFileWriter(log_path+"alipay_log_"+System.currentTimeMillis()+".txt");
writer.write(sWord);
}catch(Exceptione) {
e.printStackTrace();
}finally{
if(writer!=null) {
try{
writer.close();
}catch(IOExceptione) {
e.printStackTrace();
}
}
}
}
}
订单系统编写Service
shop-order的OrderService.java
/**
* 通过订单编号查询订单
* @param orderSn
* @return
*/
OrderselectOrderByOrderSn(StringorderSn);
shop-order的OrderServiceImpl.java
/**
* 通过订单编号查询订单
* @param orderSn
* @return
*/
@Override
publicOrderselectOrderByOrderSn(StringorderSn) {
//创建查询对象
OrderExampleexample=newOrderExample();
//创建查询条件
example.createCriteria().andOrderSnEqualTo(orderSn);
//查询
List<Order>orders=orderMapper.selectByExample(example);
if(CollectionUtils.isEmpty(orders)){
returnnull;
}
returnorders.get(0);
}
订单系统编写Controller
通过学习支付宝DEMO,发现核心的业务逻辑写在alipay.trade.page.pay.jsp中
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<metahttp-equiv="Content-Type"content="text/html; charset=utf-8">
<title>付款</title>
</head>
<%@pagelanguage="java"contentType="text/html; charset=utf-8"pageEncoding="utf-8"%>
<%@pageimport="com.alipay.config.*"%>
<%@pageimport="com.alipay.api.*"%>
<%@pageimport="com.alipay.api.request.*"%>
<%
//获得初始化的AlipayClient
AlipayClientalipayClient=newDefaultAlipayClient(AlipayConfig.gatewayUrl,AlipayConfig.app_id,AlipayConfig.merchant_private_key,"json",AlipayConfig.charset,AlipayConfig.alipay_public_key,AlipayConfig.sign_type);
//设置请求参数
AlipayTradePagePayRequestalipayRequest=newAlipayTradePagePayRequest();
alipayRequest.setReturnUrl(AlipayConfig.return_url);
alipayRequest.setNotifyUrl(AlipayConfig.notify_url);
//商户订单号,商户网站订单系统中唯一订单号,必填
Stringout_trade_no=newString(request.getParameter("WIDout_trade_no").getBytes("ISO-8859-1"),"UTF-8");
//付款金额,必填
Stringtotal_amount=newString(request.getParameter("WIDtotal_amount").getBytes("ISO-8859-1"),"UTF-8");
//订单名称,必填
Stringsubject=newString(request.getParameter("WIDsubject").getBytes("ISO-8859-1"),"UTF-8");
//商品描述,可空
Stringbody=newString(request.getParameter("WIDbody").getBytes("ISO-8859-1"),"UTF-8");
alipayRequest.setBizContent("{\"out_trade_no\":\""+out_trade_no+"\","
+"\"total_amount\":\""+total_amount+"\","
+"\"subject\":\""+subject+"\","
+"\"body\":\""+body+"\","
+"\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
//若想给BizContent增加其他可选请求参数,以增加自定义超时时间参数timeout_express来举例说明
//alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
// + "\"total_amount\":\""+ total_amount +"\","
// + "\"subject\":\""+ subject +"\","
// + "\"body\":\""+ body +"\","
// + "\"timeout_express\":\"10m\","
// + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
//请求参数可查阅【电脑网站支付的API文档-alipay.trade.page.pay-请求参数】章节
//请求
Stringresult=alipayClient.pageExecute(alipayRequest).getBody();
//输出
out.println(result);
%>
<body>
</body>
</html>
index.jsp发起请求调用alipay.trade.page.pay.jsp核心代码
经过修改编写为商城可用的代码
shop-order的OrderController.java
/**
* 去付款
* @param request
* @param model
* @param orderSn
* @return
*/
@RequestMapping("payment")
public String payment(HttpServletRequest request, Model model, String orderSn) {
try {
//获得初始化的AlipayClient
AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.gatewayUrl, AlipayConfig.app_id,
AlipayConfig.merchant_private_key, "json", AlipayConfig.charset, AlipayConfig.alipay_public_key,
AlipayConfig.sign_type);
//设置请求参数
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
alipayRequest.setReturnUrl(AlipayConfig.return_url);
alipayRequest.setNotifyUrl(AlipayConfig.notify_url);
Order order = orderService.selectOrderByOrderSn(orderSn);
//商户订单号,商户网站订单系统中唯一订单号,必填
String out_trade_no = orderSn;
//付款金额,必填
String total_amount = String.valueOf(order.getOrderAmount());
//订单名称,必填
String subject = "用户为" + order.getUserId() + "的订单";
//商品描述,可空
String body = " ";
alipayRequest.setBizContent("{\"out_trade_no\":\"" + out_trade_no + "\","
+ "\"total_amount\":\"" + total_amount + "\","
+ "\"subject\":\"" + subject + "\","
+ "\"body\":\"" + body + "\","
+ "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
//若想给BizContent增加其他可选请求参数,以增加自定义超时时间参数timeout_express来举例说明
//alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
// + "\"total_amount\":\""+ total_amount +"\","
// + "\"subject\":\""+ subject +"\","
// + "\"body\":\""+ body +"\","
// + "\"timeout_express\":\"10m\","
// + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
//请求参数可查阅【电脑网站支付的API文档-alipay.trade.page.pay-请求参数】章节
//请求
String result = alipayClient.pageExecute(alipayRequest).getBody();
System.out.println(result);
model.addAttribute("result", result);
return "order/payment";
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 跳转到我的订单页面
*
* @return
*/
@RequestMapping("myOrder")
public String myOrder() {
/**
* 这边要处理对应订单对象里的状态
*/
System.out.println("同步回调成功,跳转订单页面");
return "order/myOrder";
}
输出的内容是一个表单加上一个javascript脚本,脚本目的在于提交表单。所以实际开发中我们只需要拿到这个返回结果,把结果传给前端,使表单自动提交,就会跳转至支付宝的支付页面了。
shop-order的order/payment.ftl
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
${result}
</body>
</html>
订单系统页面处理
shop-order的order/submitOrder.ftl
<form id="paymentForm" action="${ctx}/order/payment" method="post">
<input type="hidden" name="orderSn" value="${orderSn}"/>
<div class="osuccess">
<strong class="tit">您的订单提交成功!</strong>
<p>我们讲为您保留订单2日,如果2日后EGO网仍未收到您的付款,我们将自动取消此订单。<br/>商品预计到达时间:2011年11月06日,星期日,下午14:00-18:00,请您注意查收。</p>
<div class="ddinfo">
订单编号:${orderSn} <a class="blue" href="#">查看订单详情</a>
<br/>付款方式:在线支持 中国银行<br/>应付金额:<strong class="red">¥${totalPrice}</strong>
</div>
<p class="gopay">
<a href="javascript:void(0);" onclick="payment();">
<img src="${ctx}/static/images/gopay.gif" width="88" height="36" alt=""/>
</a>
</p>
<div class="nowgo">您现在还可以:<br/><a class="blue" href="#">返回首页</a><a class="blue" href="#">继续购物</a><a
class="blue" href="#">查看订单</a><a class="blue" href="#">订单中心</a></div>
</div><!--osuccess end-->
</form>
<script type="text/javascript">
// 去付款
function payment() {
$("#paymentForm").submit();
}
</script>
将我的订单.html放入shop-order/order/myOrder.ftl并进行简单处理,主要是看同步回调是否能够顺利跳转
测试
前台系统添加至购物车信息,点击去结算
跳转至订单系统生成预订单,准备结算,点击提交订单
清空购物车,准备去付款,点击去付款
跳至支付宝进行支付
支付流程结束,正常跳转回前台系统
(正常环境是进入已购买商品页,显示订单信息,购买商品信息,发货时间等等,而且在进入已购买商品页的请求过程中修改订单状态,支付状态,物流,发货时间等等相关信息)
注:以上只是测试同步通知环境搭建成功,商城公测以后我们必须要保证异步通知环境可用
异步通知环境搭建
理解
首先我们需要知道为什么需要异步通知,支付宝提供了同步通知和异步通知两种方式,同步通知和异步通知的业务逻辑代码是一致的(比如在我们商城中,我们需要修改订单状态,支付状态,物流,发货时间等等相关信息)。
同步通知——用户支付完成之后跳转成功页面给用户展示已购买信息...等等
异步通知——用户支付完成之后,支付宝会根据API中商户传入的notify_url,通过POST请求的形式将支付结果作为参数通知到商户系统。说白了就是告诉支付宝支付成功还是失败了。
但是我们学习时都是内网环境,无法实现公网环境,所以需要借助第三方工具。
7.2、NATAPP基于ngrok的国内高速内网穿透服务
点击下载选择适用于自己电脑的版本
NATAPP1分钟快速新手图文教程
根据https://natapp.cn/article/natapp_newbie快速上手
手机号注册
完成注册
实名认证
免费隧道
名称自定义,端口误冲突,点击免费购买
购买成功
配置config.ini(推荐)
文件下载地址:https://natapp.cn/article/config_ini
将本文件放置于natapp同级目,将免费隧道复制至authtoken即可
#将本文件放置于natapp同级目录 程序将读取 [default] 段
#在命令行参数模式如 natapp -authtoken=xxx 等相同参数将会覆盖掉此配置
#命令行参数 -config= 可以指定任意config.ini文件
[default]
authtoken= #对应一条隧道的authtoken
clienttoken= #对应客户端的clienttoken,将会忽略authtoken,若无请留空,
log=none #log 日志文件,可指定本地文件, none=不做记录,stdout=直接屏幕输出 ,默认为none
loglevel=ERROR #日志等级 DEBUG, INFO, WARNING, ERROR 默认为 DEBUG
http_proxy= #代理设置 如 http://10.123.10.10:3128 非代理上网用户请务必留空
注 http_proxy 用于无法直接上网,需要通过代理才能上网的用户设置,其他用户请留空
最终配置结果如下
启动natapp.exe
启动以后会得到一个公网地址,该公网地址映射我们的127.0.0.1:9091
前台系统编写Controller
shop-portal的OrderController.java
/**
* 订单系统完成后回调方法
* @param model
* @return
*/
@RequestMapping("callback")
public String callback(Model model){
//我们在这里需要对订单对象进行操作处理订单状态等一系列操作
System.out.println("订单支付成功");
model.addAttribute("result","success");
return "order/callback";
}
前台系统页面处理
shop-portal的callback.ftl
<!-- 设置项目根路径全局变量 -->
<#assign ctx=request.contextPath/>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- 该页面的结果是专门给支付宝用的 -->
${result}
</body>
</html>
订单系统修改支付宝配置类
shop-order的AlipayConfig.java
// 服务器异步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
public static String notify_url = "http://yba6sc.natappfree.cc/shop-portal/order/callback";
测试
同步通知——订单系统是否正常跳转至前台系统
异步通知——控制台是否输出了支付成功!
两条分支都走通了,说明支付宝的支付环境已经搭建成功。