Vue+Springboot如何对接支付宝沙箱,实现支付功能

本文最后更新于 2023年9月17日 上午

又是我!!!头图!

作者:Mintimate

博客:https://www.mintimate.cn
Mintimate’s Blog,只为与你分享

前言

如何在前端Vue,后端Springboot情况下,适配支付宝沙箱呢?使用沙箱模拟支付宝支付环境,若要切换为正式的支付宝环境,所要修改的代码极少嗷。

本文参考:

准备工作

首先需要注册支付宝开发平台的账号,注册地址:https://open.alipay.com/
支付宝开发者

之后进入开发者空间:https://open.alipay.com/dev/workspace,找到沙箱功能:
沙箱基础设置

这里我们可以获取:APPID,并可以设置RSA2密钥

接口加签方式就可以了,一定要先设置,英文支付宝上可能有几个小时的更新延时,在延时期间我们测试,即使密钥准确也会报错……

设置方法官方写的太详细了,可以参考:https://opendocs.alipay.com/common/02kipl

最后需要注意,公钥是要支付宝公钥:

支付宝公钥

到此,我们前期准备完成,准备了:

  • APPID:应用信息的APPID。
  • 私钥:我们生成的RSA2密钥的私钥。
  • 支付宝公钥:我们提供公钥后,支付宝返还的支付宝公钥。

上述三个参数,后续都会用到。

Maven引用SDK

根据支付宝开发者文档,我们需要引用Java的SDK:

1
2
3
4
5
6
<!--支付宝支付-->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.22.37.ALL</version>
</dependency>

Springboot配置

支付宝配置

这里实现配置类的方法很多,你可以编写配置类,用IO容器注入;我这里使用的是简单的编写个constantc常量类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* Desc 支付宝配置常量类
*
* @author Mintimate
*/
public class AlipayConfig {
// 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
public static String app_id = "202****";
// 私钥
public static String merchant_private_key = "MIIE******";
// 支付宝公钥。
public static String alipay_public_key = "MIIBIj*****";
// 服务器异步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
/**
* 返回的时候此页面不会返回到用户页面,只会执行你写到控制器里的地址
*/
public static String notify_url = "https://****/notifyUrl";
// 页面跳转同步通知页面路径
*/
public static String return_url = "http://*****/alipay/returnUrl";
// 签名方式
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 log_path = "";
}

其中:

  • notify_url:用户支付成功后,发送成功的通知消息消息地址(一般返回给后端)。
  • return_url:用户支付成功后,重定向的地址。

Controller层

Controller接受前端请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/**
* Desc 外接支付控制器
*
* @author Mintimate
*/
@RestController
public class PayController {
@Resource
private PayService payService;

/**
* 接受订单信息
* @param alipayOrderVO:订单信息
* @return
*/
@RequestMapping("/alipay/pay")
@ResponseBody
public Result payByAlipay(@RequestBody AlipayOrderVO alipayOrderVO) {
// 商户网站唯一订单号
String out_trade_no = UuidUtil.getTimeBasedUuid().toString();
alipayOrderVO.setOut_trade_no(out_trade_no);
String redirect=payService.saveOrderInfo(alipayOrderVO);
if (redirect!=null){
return Result.ok(redirect);
}
else {
return Result.fail("支付参数错误");
}
}

/**
* 用户支付成功后,支付宝调用回调信息
* @param request
* @param response
* @return
*/
@RequestMapping("/alipay/notifyUrl")
public void notifyUrl(HttpServletRequest request, HttpServletResponse response) {
String out_trade_no=null;
double price = 0;
Map<String, String[]> parameterMap = request.getParameterMap();
for (String s : parameterMap.keySet()) {
String[] strings = parameterMap.get(s);
for (int i = 0; i < strings.length; i++) {
if (s.equals("out_trade_no")){
out_trade_no=strings[i];
}
if (s.equals("total_amount")){
price= Double.parseDouble(strings[i]);
}
}
}
payService.finishPayment(out_trade_no,price);
}

/**
* 支付成功后重定向页面:关闭当前页面
* @param request
* @param response
* @return
*/
@RequestMapping("/alipay/returnUrl")
public String returnUrl(HttpServletRequest request, HttpServletResponse response) {
return "<script>" +
"window.opener = null;" +
"window.open(\"\", \"_self\");" +
"window.close();" +
"</script>";
}



}

其中:

  • Result为我的结果分装类,如果你没自己的结果封装,可以使用String类型返回给前台
  • AlipayOrderVO为前端传入的结算信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* Desc 支付宝商品结算信息
*
* @author Mintimate
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AlipayOrderVO {
/**
* 支付编号
*/
private String out_trade_no;
/**
* 商品名称
*/
private String subject;
/**
* 订单总金额
*/
private double total_amount;
/**
* 支付宝常量
*/
private String product_code="FAST_INSTANT_TRADE_PAY";

}

接下来就看业务服务层了。

Service层

Service我就不展示过多代码了,首先是上文提到的saveOrderInfo方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public String saveOrderInfo(AlipayOrderVO alipayOrderVO) {
if (Objects.isNull(alipayOrderVO)){
throw new dataException("订单信息不可为空");
}
// 订单号存入Redis,request由IOC容器注入
redisUtil.hset(IMG2D_ALIPAY_ORDER,alipayOrderVO.getOut_trade_no(),request.getAttribute("userID"));
//获得初始化的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);
try {
alipayRequest.setBizContent(JSON.toJSONString(alipayOrderVO));
//请求
String result = alipayClient.pageExecute(alipayRequest, "GET").getBody();
return result;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

这里是根据订单信息,到支付宝处生成交易直链并访问给Controller控制器,由控制器分装成结果对象给前端Vue。

同时,订单号和用户id绑定,存入Redis缓存,等待支付宝notify回调确认。

而Service层的这个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void finishPayment(String out_trade_no, double total_amount) {
// 根据订单号,在Redis内查找`saveOrderInfo`里存储的信息
String userid= String.valueOf(redisUtil.hget(IMG2D_ALIPAY_ORDER,out_trade_no));
// 根据Redis查找的用户ID,查找用户
User user=userDao.selectById(userid);
if (Objects.isNull(user)){
throw new dataException("订单获取用户失败!!");
}
// 数据库内用户金额的封装对象,准备在数据库内添加对象金额
UserBalance userBalance= userBalanceDao.selectOne(new LambdaQueryWrapper<UserBalance>()
.eq(UserBalance::getUserId,userid));
// 余下内容,按自己业务需求设计咯
......
}

这样,我们的支付宝沙线业务的后端就搭建好了。在写前端前,我们使用PAW进行测试,你也可以使用Postman等工具进行测试。

PAW接口测试

接下来我们使用PAW进行接口测试:
在PAW内测试

把回调复制到浏览器内,无痕浏览:
在浏览器内测试

同时,Redis内也有订单记录:
在Redis内测试

之后,就可以使用沙箱版本支付宝付款了。需要注意,支付成功后,会发生请求到你填写的notify内,并重定向到你填写的returnUrl内。

Vue内配置

Vue内配置就简单了,简单地说,前端封装商品名称和商品订单金额给后端并解析后端发回的对象(也就是支付宝付款页面)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
handlerToken(newValue){
this.showChargeToken=false
successTips("请在新窗口内完成支付宝的支付(*≧ω≦)")
post("/img2d/alipay/pay",newValue).then(({data})=>{
if(data.flag){
// console.log(data.data)
window.open(data.data)
}
})
this.$buefy.dialog.confirm({
message: '已经完成支付?现在获取最新数据?',
cancelText:'并没有',
confirmText:'已支付',
onConfirm: () => {
this.$buefy.toast.open('正在获取最新数据')
this.getLatestToken()
}
})
}

其中的newValue对象为:

1
2
3
4
userBalance:{
historyBalance:"--",
balance:"--"
},

前端UI代码就不方便展示了,我觉得展示这样,应该都知道怎么去适配了。
最后看看效果:
前端UI_1
选择“钞能力”后,可以选择支付金额:
前端UI_2
提交订单,会在新窗口内打开支付宝交易页面,页面保留一个支付完成确认:
前端UI_2
如果用户选择已完成,前端拉取数据库/Redis内最新数据。

END

以上就是支付宝沙箱适配的全部流程了。可能会随着支付宝沙箱版本升级而有所不同,需要依照自己情况不同做出改变了。

还有就是,支付宝密钥一定要最开始就部署,因为支付宝的更新可能有网络延时。测试沙箱环境,要开启浏览器的无痕模式哦,否则浏览器可能判断沙箱网站为盗版网站。



Vue+Springboot如何对接支付宝沙箱,实现支付功能
https://www.mintimate.cn/2022/02/25/alipaySandbox/
作者
Mintimate
发布于
2022年2月25日
许可协议