一.准备工作
登录支付宝开放平台进行扫码登录
登录后对个人信息填写
右上角进入管理中心
选择研发服务
进入界面对密钥进行设置
下载工具
下载密钥生成工具
手机下载支付宝沙箱版,用于测试时付款
开发思想
来自官网
调用顺序如下:
- 商户系统请求支付宝接口 alipay.trade.page.pay,支付宝对商户请求参数进行校验,而后重新定向至用户登录页面。
- 用户确认支付后,支付宝通过 get 请求 returnUrl(商户入参传入),返回同步返回参数。
- 交易成功后,支付宝通过 post 请求 notifyUrl(商户入参传入),返回异步通知参数。
- 若由于网络等问题异步通知没有到达,商户可自行调用交易查询接口 alipay.trade.query
进行查询,根据查询接口获取交易以及支付信息(商户也可以直接调用查询接口,不需要依赖异步通知)。
注意:
-
由于同步返回的不可靠性,支付结果必须以异步通知或查询接口返回为准,不能依赖同步跳转。
-
商户系统接收到异步通知以后,必须通过验签(验证通知中的 sign 参数)来确保支付通知是由支付宝发送的。详细验签规则参考异步通知验签。
-
接收到异步通知并验签通过后,一定要检查通知内容,包括通知中的 app_id、out_trade_no、total_amount
是否与请求中的一致,并根据 trade_status 进行后续业务处理。 -
在支付宝端,partnerId 与 out_trade_no 唯一对应一笔单据,商户端保证不同次支付 out_trade_no
不可重复;若重复,支付宝会关联到原单据,基本信息一致的情况下会以原单据为准进行支付
。
二. Maven项目创建
注意:该案例只是做一个简单的测试不连接数据库
项目结构
导入pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.jiang</groupId>
<artifactId>alipay</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>alipay</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入thymeleaf的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alipay.sdk/alipay-sdk-java -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.9.124.ALL</version>
</dependency>
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
<version>1.9.22</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件
application.yml
server:
port: 8080
spring:
profiles:
active: dev
application-dev.yml
spring:
thymeleaf:
prefix: classpath:/templates/
suffix: .html
mode: HTML5
encoding: UTF-8
servlet:
content-type: text/html
cache: false
配置类的编写
应用ID(APPID):在准备工作时,信息配置中获取,每个用户的不一样
商户私钥(merchant_private_key ):在使用密钥生成工具时,会得到
支付宝网关(gatewayUrl ):沙箱环境下的网关与正式开发的网关有所区别
界面跳转(notify_url | return_url ):在支付成功之后,跳转到指定界面,因为要外网能访问的到,所以我们可以下载一个网络穿透工具
public class AlipayConfig {
// 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
public static String app_id = "";
// 商户私钥,您的PKCS8格式RSA2私钥
public static String merchant_private_key = "";
// 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
public static String alipay_public_key = "";
// 服务器异步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
public static String notify_url = "http://48bfwd.natappfree.cc/Pay/notify_url";
// 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
public static String return_url = "http://48bfwd.natappfree.cc/Pay/return_url";
// 签名方式
public static String sign_type = "RSA2";
// 字符编码格式
public static String charset = "utf-8";
// 支付宝网关
public static String gatewayUrl = "https://openapi.alipaydev.com/gateway.do";
public static String format = "json";
//初始化设置
public AlipayClient getClient() {
AlipayClient alipayClient = new DefaultAlipayClient
(AlipayConfig.gatewayUrl,
AlipayConfig.app_id,
AlipayConfig.merchant_private_key,
AlipayConfig.format,
AlipayConfig.charset,
AlipayConfig.alipay_public_key,
AlipayConfig.sign_type);
return alipayClient;
}
}
网络穿透工具natapp配置
1.natapp的下载
并且在该网站上注册
购买一个免费隧道
在我的隧道中查看authtoken,将其复制
2.natapp的配置
在natapp目录下创建config.ini文件
[default]
authtoken=在此处添加隧道的authtoken
clienttoken=
log=none
loglevel=ERROR
http_proxy=
保存,退出,打开nataapp.exe
接口编写
public interface AlipayService {
/**
* 网页扫码支付
* @param amount 金额
* @param trade_no 订单号
* @return
*/
AlipayTradePagePayResponse PCPay(String amount,String trade_no) throws AlipayApiException;
/**
* 退款
* @param refund_amount 退款金额,退款时要对传入金额进行验证,不可以大于商品的原价
* @param trade_no 订单号
* @return
* @throws AlipayApiException
*/
AlipayTradeRefundResponse Refund(String refund_amount, String trade_no) throws AlipayApiException;
}
接口实现
@Service
public class AlipayServiceImpl implements AlipayService {
//当天未支付,订单超时
private String timeout_express = "1h";
private AlipayClient alipayClient = new AlipayConfig().getClient();
/**
* 电脑网站支付(PC支付)
* @param total_amount 订单金额
* @param trade_no 商家订单号
* @param subject 订单标题
* @return
* @throws AlipayApiException
*/
@Override
public AlipayTradePagePayResponse PCPay(String total_amount,String trade_no) throws AlipayApiException {
//设置请求参数
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
//设置支付成功跳转界面
alipayRequest.setReturnUrl(AlipayConfig.return_url);
alipayRequest.setNotifyUrl(AlipayConfig.notify_url);
//因为subject是必填参数,为了简单起见,将其固定
String subject = "test";
// product_code设置的是实时到账方式,当前支付宝仅支持这种方式
alipayRequest.setBizContent("{\"out_trade_no\":\""+ trade_no +"\","
+ "\"total_amount\":\""+ total_amount +"\","
+ "\"subject\":\""+ subject +"\","
+ "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
//请求
AlipayTradePagePayResponse result = alipayClient.pageExecute(alipayRequest);
return result;
}
@Override
public AlipayTradeRefundResponse Refund(String refund_amount, String trade_no) throws AlipayApiException {
AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
String id = trade_no +"1";
request.setBizContent("{" +
"\"out_trade_no\":\""+trade_no+"\"," +
"\"refund_amount\":\""+refund_amount+"\"," +
"\"refund_reason\":\"正常退款\"," +
"\"out_request_no\":\""+id+"\"}");
AlipayTradeRefundResponse response = alipayClient.execute(request);
return response;
}
}
控制层的编写
案例简单,未对支付成功,退款成功之后的业务进行编写
/**
* 支付宝支付功能
*/
@Controller
@RequestMapping("/Pay")
public class AlipayController {
@Autowired
private AlipayService alipayService;
/**
* PC端支付
* @param total_amount 金额
* @param trade_no 商品的单号
* @return 返回一个支付宝二维码界面
* @throws AlipayApiException
*/
@RequestMapping(value = "/PCPay", method = RequestMethod.POST)
@ResponseBody
public JSONObject PCPay(String total_amount, String trade_no) throws AlipayApiException {
System.out.println("/PCPay");
JSONObject jsonObject = new JSONObject();
AlipayTradePagePayResponse alipayTradePagePayResponse = alipayService.PCPay(total_amount,trade_no);
jsonObject.put("msg", alipayTradePagePayResponse);
return jsonObject;
}
/**
* 退款
* @param refund_amount 退款金额
* @param trade_no 商品的单号
* @return
* @throws AlipayApiException
*/
@RequestMapping(value = "/Refund", method = RequestMethod.GET)
@ResponseBody
public JSONObject Refund(String refund_amount,String trade_no) throws AlipayApiException {
System.out.println("Refund");
JSONObject jsonObject = new JSONObject();
AlipayTradeRefundResponse refund = alipayService.Refund(refund_amount, trade_no);
jsonObject.put("msg", refund);
return jsonObject;
}
@RequestMapping(value = "/toRefund")
public String toRefund(){
System.out.println("toRefund");
return "thymeleaf/refund";
}
@RequestMapping("index")
public String index(ModelMap map) {
return "thymeleaf/index";
}
/**
* 异步调用接口
* @return
*/
@RequestMapping("notify_url")
public String notify_url() {
return "thymeleaf/notify_url";
}
/**
* 同步调用接口
* @param request
* @return
*/
@RequestMapping("return_url")
public String return_url(HttpServletRequest request) {
Map<String,String[]> requestParams = request.getParameterMap();
return "thymeleaf/return_url";
}
}
thymeleaf模板
index.html
涉及付款功能,退款界面跳转
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div class="amount_total">5</div>
<button class="pay">付款</button><br/>
<a href="/Pay/toRefund">想要退款</a>
</body>
<script type="text/javascript" src="/js/jquery-3.5.1.min.js"></script>
<script>
var trade_no = 2020;
var amount = $('.amount_total').text();
init();
function init() {
var trade_no_lenth = 8;
for (var i = 0; i < trade_no_lenth; i++) {
trade_no += parseInt(Math.random() * 9)+'';
}
$('.amount_total').append('<a>商品单号:'+trade_no+'</a>')
}
// var amount = document.getElementsByClassName('amount_total')
$('.pay').on('click',function () {
console.log(amount)
console.log(trade_no)
$.ajax({
url: "http://localhost:8080/Pay/PCPay",
type: 'POST',
dataType: 'json',
data: ({
"total_amount": amount,
"trade_no": trade_no
}),
success: function (msg) {
console.log(msg)
console.log(msg.msg.body)
$('.pay').append(msg.msg.body)
},
error: function (e) {
console.log(e)
}
})
});
</script>
</html>
refund.html
付款功能
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
单号:<input class="trade_no"/>
退款金额:<input class="refund_amount"/>
<button class="refund">退款</button>
</body>
<script type="text/javascript" src="/js/jquery-3.5.1.min.js"></script>
<script type="text/javascript">
$('.refund').on('click', function () {
var refund_amount = $('.refund_amount').val()
var trade_no = $('.trade_no').val()
console.log(refund_amount,trade_no)
$.ajax({
url: "http://localhost:8080/Pay/Refund",
type: 'GET',
dataType: 'json',
data: ({
"refund_amount": refund_amount,
"trade_no": trade_no
}),
success: function (msg) {
console.log(msg)
},
error: function (e) {
console.log(e)
}
})
})
</script>
</html>
其他功能的实现
三.功能展示
付款功能
退款功能