码云地址:https://gitee.com/chenbz2/zhifubao
支付宝出了新的SDK,网上大部分教程是旧版的SDK,都可以用,看你选择哪个。
教程都会让你设置return_url 和 notify_url 地址,首先要了解这两个的区别,return_url 是同步调用,是支付成功后自动跳转过去的,notify_url 是异步调用需要返回 success 给支付宝,return_url 支付宝通知是GET请求,notify_url 支付宝通知是POST。
参考: 异步通知参数说明、支付宝异步回调
沙箱应用
1、工具下载
2、打开工具
1、复制应用公钥
2、打开 沙箱应用 设置支付宝公钥
3、选择加签模式(公钥)
4、粘贴应用公钥
1、下载 ngrok
下载可能有点慢,有条件的梯子下载
2、解压 ngrok
3、启动 ngrok
cd ngrok所在路径
ngrok http 8866
这行命令是把 8866 端口穿透出去,因为我们接下来要建的项目端口是 8866
这个网址就是我们穿透出去的外网访问链接,窗口不要关掉,保留着
新建springboot项目,项目结构如下
com.alipay.sdk
alipay-easysdk
2.1.0
## 端口
server.port= 8866
## 支付宝配置
# 应用ID
alipay.appId = 把你的应用id填到这里
# 应用私钥
alipay.privateKey = 把你的应用私钥填到这里
# 支付宝公钥,注意不是生成的应用公钥
alipay.publicKey = 把你的支付宝公钥填到这里
#支付网关配置,这一项是写死的,正式环境是openapi.alipay.com
alipay.gateway = openapi.alipaydev.com
# 支付宝前台跳转地址
alipay.returnUrl = 外网穿透链接/return_url.html
# 支付宝后台通知地址
alipay.notifyUrl = 外网穿透链接/api/alipay/notify_url
# 支付宝前台手机网页支付中途取消跳转地址
alipay.errorUrl = 外网穿透链接/error_url.html
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class AlipayConfig implements ApplicationRunner {
// 应用id
@Value("${alipay.appId}")
private String appId;
// 私钥
@Value("${alipay.privateKey}")
private String privateKey;
// 公钥
@Value("${alipay.publicKey}")
private String publicKey;
// 支付宝网关
@Value("${alipay.gateway}")
private String gateway;
// 支付成功后的接口回调地址,不是回调的友好页面,不要弄混了
@Value("${alipay.notifyUrl}")
private String notifyUrl;
/**
* 项目初始化事件
* */
@Override
public void run(ApplicationArguments args) throws Exception {
//初始化支付宝SDK
Factory.setOptions(getOptions());
System.out.println("**********支付宝SDK初始化完成**********");
}
private Config getOptions() {
//这里省略了一些不必要的配置,可参考文档的说明
Config config = new Config();
config.protocol = "https";
config.gatewayHost = this.gateway;
config.signType = "RSA2";
config.appId = this.appId;
// 为避免私钥随源码泄露,推荐从文件中读取私钥字符串而不是写入源码中
config.merchantPrivateKey = this.privateKey;
// 注:如果采用非证书模式,则无需赋值上面的三个证书路径,改为赋值如下的支付宝公钥字符串即可
config.alipayPublicKey = this.publicKey;
// 可设置异步通知接收服务地址(可选)
config.notifyUrl = notifyUrl;
return config;
}
}
支付宝参考文档
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
public class OrderUtil {
/**
* 根据时间戳生成订单号
* */
public static String getOrderNo () {
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
LocalDateTime localDateTime = Instant.ofEpochMilli(System.currentTimeMillis()).atZone(ZoneOffset.ofHours(8)).toLocalDateTime();
return df.format(localDateTime);
}
}
public interface AlipayService {
/**
* @description: 支付宝电脑网页支付
* @param subject: 订单名称
* @param total: 金额
* @date: 2020/11/3
* @return java.lang.String
*/
String page(String subject, String total);
/**
* @description: 支付宝手机网页支付
* @param subject: 订单名称
* @param total: 金额
* @date: 2020/11/3
* @return java.lang.String
*/
String wap(String subject, String total);
/**
* @description: 支付宝退款
* @param outTradeNo: 商家订单号
* @param refundAmount: 退款金额(不能大于交易金额)
* @date: 2020/11/3
* @return java.lang.String
*/
String refund(String outTradeNo, String refundAmount);
}
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.payment.common.models.AlipayTradeRefundResponse;
import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse;
import com.alipay.easysdk.payment.wap.models.AlipayTradeWapPayResponse;
import com.chenbz.zhifubao.system.service.AlipayService;
import com.chenbz.zhifubao.util.OrderUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class AlipayServiceImpl implements AlipayService {
// 支付成功后要跳转的页面
@Value("${alipay.returnUrl}")
private String returnUrl;
// 支付宝前台手机网页支付中途取消跳转地址
@Value("${alipay.errorUrl}")
private String errorUrl;
@Override
public String page(String subject, String total) {
try {
AlipayTradePagePayResponse response = Factory.Payment
// 选择电脑网站
.Page()
// 调用支付方法(订单名称, 商家订单号, 金额, 成功页面)
.pay(subject, OrderUtil.getOrderNo(), total, returnUrl);
return response.body;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public String wap(String subject, String total) {
try {
AlipayTradeWapPayResponse response = Factory.Payment
//选择手机网站
.Wap()
// 调用支付方法(订单名称, 商家订单号, 金额, 中途退出页面, 成功页面)
.pay(subject, OrderUtil.getOrderNo(), total, errorUrl, returnUrl);
return response.body;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public String refund(String outTradeNo, String refundAmount) {
try {
AlipayTradeRefundResponse response = Factory.Payment
.Common()
// 调用交易退款(商家订单号, 退款金额)
.refund(outTradeNo, refundAmount);
if (response.getMsg().equals("Success")) {
return "退款成功";}
} catch (Exception e) {
e.printStackTrace();
}
return "退款失败";
}
}
API的详细参数说明
API的更加详细参数说明
import com.alipay.easysdk.factory.Factory;
import com.chenbz.zhifubao.system.service.AlipayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/alipay")
public class AlipayController {
private final AlipayService alipayService;
@Autowired
public AlipayController(AlipayService alipayService) {
this.alipayService = alipayService;
}
/**
* @description: 支付宝电脑网页支付
* @param subject: 订单名称
* @param total: 金额
* @date: 2020/11/3
* @return java.lang.String
*/
@PostMapping("/page")
public String page(String subject, String total) {
subject = "测试支付";
total = "1000";
return alipayService.page(subject, total);
}
/**
* @description: 支付宝手机网页支付
* @param subject: 订单名称
* @param total: 金额
* @date: 2020/11/3
* @return java.lang.String
*/
@PostMapping("/wap")
public String wap(String subject, String total) {
subject = "测试支付";
total = "1000";
return alipayService.wap(subject, total);
}
/**
* @description: 支付宝异步回调
* @param request: 请求
* @date: 2020/11/3
* @return java.lang.String
*/
@PostMapping("/notify_url")
public String notify_url(HttpServletRequest request) throws Exception {
if (request.getParameter("trade_status").equals("TRADE_SUCCESS")) {
System.out.println("=========支付宝异步回调========");
Map<String, String> params = new HashMap<>();
Map<String, String[]> requestParams = request.getParameterMap();
for (String name : requestParams.keySet()) {
params.put(name, request.getParameter(name));
// System.out.println(name + " = " + request.getParameter(name));
}
// 支付宝验签
if (Factory.Payment.Common().verifyNotify(params)) {
// 验签通过
System.out.println("交易名称: " + params.get("subject"));
System.out.println("交易状态: " + params.get("trade_status"));
System.out.println("支付宝交易凭证号: " + params.get("trade_no"));
System.out.println("商户订单号: " + params.get("out_trade_no"));
System.out.println("交易金额: " + params.get("total_amount"));
System.out.println("买家在支付宝唯一id: " + params.get("buyer_id"));
System.out.println("买家付款时间: " + params.get("gmt_payment"));
System.out.println("买家付款金额: " + params.get("buyer_pay_amount"));
}
}
return "success";
}
/**
* @description: 支付宝退款
* @param outTradeNo: 商家订单号
* @param refundAmount: 退款金额(不能大于交易金额)
* @date: 2020/11/3
* @return java.lang.String
*/
@PostMapping("/refund")
public String refund(String outTradeNo, String refundAmount) {
return alipayService.refund(outTradeNo, refundAmount);
}
}
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试支付title>
head>
<body>
<div>
<h2>测试支付h2>
<form enctype="multipart/form-data" action="/api/alipay/page" method="post">
<button type="submit">电脑确认支付button>
form>
<form enctype="multipart/form-data" action="/api/alipay/wap" method="post">
<button type="submit">手机确认支付button>
form>
div>
body>
html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>支付成功title>
head>
<body>
<div>
<h2>支付成功h2>
<a href="外网穿透链接">主页a>
div>
body>
html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>支付失败title>
head>
<body>
<div>
<h2>支付失败h2>
div>
body>
html>
1、下载沙箱app
2、获取沙箱账号