1. 什么是支付宝扫码支付?
现如今,手机支付已相当普遍,而作为开发人员应该对手机支付操作有所了解。而支付宝接口是支付宝提供的一个接口,用来对接软件应用程序在进行金钱交易使用。然后对于编程爱好者而言,想学习这一点就有点难,因为要想使用支付宝接口,必须前提是使用软件应用程序,软件应用程序需要向支付宝申请,提交一系列资料,这一点是实现不了的。这就对开发者增加了一定的难度,因为产品没有上线,然后需要对接支付宝接口就是很大的问题,所以出现了沙箱环境,具有虚拟的用户和管理员账户,进行实验测试是否对接成功。
2. 使用技术 + 编程软件
- idea + SpringBoot
- vue + ElementUI + vue-Qr (vue生成二维码框架)
- Sunny-Ngrok (内网穿透服务,用于支付宝回调)
- WebSocket (实现前端响应)
3. 步骤
- 准备沙箱环境
- 后端接口配置
- 前端页面设计
- 结果测试
- 注意要点
3.1.1. 准备沙箱环境
支付宝开放平台
官网:https://open.alipay.com/platform/home.htm
进入管理中心
这里是创建应用的地方,也就是说有项目要上线时,在这里申请。
找到沙箱环境
- 将这个页面往下拉,找到研发服务,进入里面
- 沙箱环境
我这边的沙箱环境是配置好了的,需要知道如何配置沙箱环境的话可访问:
沙箱环境:https://opendocs.alipay.com/open/200/105311
注意:请将应用公钥和私钥保存好,后面会用到
3.2. 后端接口配置
接口配置前的准备
3.2.1. 引入依赖
<!-- 支付宝支付 -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.15.14.ALL</version>
</dependency>
<!-- websocket依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
3.2.2. 支付宝回调对象的封装
封装好对象,支付宝回调的时候就不需要我们手动获取参数了
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serializable;
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class AliReturnPay implements Serializable {
private static final long serialVersionUID = 8683949075381016639L;
// 开发者的app_id
private String app_id;
// 商户订单号
private String out_trade_no;
// 签名
private String sign;
// 交易状态
private String trade_status;
// 支付宝交易号
private String trade_no;
// 交易的金额
private String total_amount;
}
3.2.3. 编写 WebSocket 工具类
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
@ServerEndpoint("/bindingRecord")
@Component
@Slf4j
public class WebSocket {
private Session session;
private static CopyOnWriteArraySet<WebSocket> webSockets = new CopyOnWriteArraySet<>();
/**
* 新建webSocket配置类
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
/**
* 建立连接
* @param session
*/
@OnOpen
public void onOpen(Session session) {
this.session = session;
webSockets.add(this);
log.info("【新建连接】,连接总数:{}", webSockets.size());
}
/**
* 断开连接
*/
@OnClose
public void onClose(){
webSockets.remove(this);
log.info("【断开连接】,连接总数:{}", webSockets.size());
}
/**
* 接收到信息
* @param message
*/
@OnMessage
public void onMessage(String message){
log.info("【收到】,客户端的信息:{},连接总数:{}", message, webSockets.size());
}
/**
* 发送消息
* @param message
*/
public void sendMessage(String message){
log.info("【广播发送】,信息:{},总连接数:{}", message, webSockets.size());
for (WebSocket webSocket : webSockets) {
try {
webSocket.session.getBasicRemote().sendText(message);
} catch (IOException e) {
log.info("【广播发送】,信息异常:{}", e.fillInStackTrace());
}
}
}
}
3.2.4. 支付接口配置
3.2.4.1. 编辑支付接口
@Autowired
private WebSocket webSocket; // 导入刚刚写好的 WebSocket 工具类
// 支付宝网关:沙箱环境 (真实环境的话改外:https://openapi.alipay.com/gateway.do)
private static final String URL = "https://openapi.alipaydev.com/gateway.do";
// APPID (请自行填写,真实环境请做对应修改)
private static final String APP_ID = "";
// 应用私钥 (请自行填写,真实环境请做对应修改)
private static final String APP_PRIVATE_KEY = "";
// 数据返回的格式 (只支持json格式)
private static final String FORMAT = "json";
// 验签编码 (根据需要修改)
private static final String CHARSET = "UTF-8";
// 支付宝公钥 (请自行填写,真实环境请做对应修改)
private static final String ALIPAY_PUBLIC_KEY = "";
// 验签加密方法 (根据需要修改)
private static final String SIGN_TYPE = "RSA2";
@ApiOperation(value = "支付宝支付 沙箱环境")
@PostMapping("/sandboxPay")
public String sandboxPay() throws AlipayApiException{
AlipayClient alipayClient = new DefaultAlipayClient(URL,APP_ID,APP_PRIVATE_KEY,FORMAT,CHARSET,ALIPAY_PUBLIC_KEY,SIGN_TYPE);
AlipayTradePrecreateRequest alipayRequest = new AlipayTradePrecreateRequest();
// 设置支付宝异步通知回调地址 (注意:这个网址必须是可以通过外网访问的网址)
alipayRequest.setNotifyUrl("");
alipayRequest.setBizContent ( "{" +
"\"out_trade_no\":\"123456\"," + // 商户订单号
"\"total_amount\":\"88.88\"," + // 商品价格
"\"subject\":\"测试\"," + // 商品标题
"\"store_id\":\"公司名\"," + // 组织或公司名
"\"timeout_express\":\"90m\"}" ); // 订单有效时间
AlipayTradePrecreateResponse response = alipayClient.execute (alipayRequest);
// 返回支付宝支付网址,用于生成二维码
String qr = response.getQrCode();
return qr;
}
注意: 支付回调网址必须是可以外网访问的网址,后面会使用 Sunny-Ngrok (内网穿透服务) 去实现
3.2.4.2. 编写支付回调接口 (异步通知)
@ApiOperation(value = "支付宝支付 异步通知")
@PostMapping("/call")
public void call(HttpServletRequest request, HttpServletResponse response, AliReturnPay aliReturnPay) throws IOException {
// 通知返回的数据会封装到 AliReturnPay 类中
response.setContentType("type=text/html;charset=UTF-8");
String orderNo = aliReturnPay.getOut_trade_no(); // 获得订单号,对数据进行修改
// 支付成功的返回码
if (("TRADE_SUCCESS").equals(aliReturnPay.getTrade_status())){
// 向前端发送一条支付成功的通知
webSocket.sendMessage("true");
}
}
注意点: 由于通知的代码只能本地访问,而支付宝通知需要一个外网可以访问的地址。我们在编码阶段项目是没有上线的,外网访问不到,这里我们就可以使用 Sunny-Ngrok (内网穿透服务) 进行测试操作
枚举名称 | 枚举说明 |
---|---|
WAIT_BUYER_PAY | 交易创建,等待买家付款。 |
TRADE_CLOSED | 未付款交易超时关闭,或支付完成后全额退款。 |
TRADE_SUCCESS | 交易支付成功。 |
TRADE_FINISHED | 交易结束,不可退款。 |
不同的枚举通知需要在对应的业务中使用
详情可参考:https://opendocs.alipay.com/open/203/105286
3.2.4.3. 实现外网访问接口 (Sunny-Ngrok)
- Sunny-Ngrok 官网:https://www.ngrok.cc/
特点
- 提供免费内网穿透服务,免费服务器支持绑定自定义域名
- 管理内网服务器,内网web进行演示
- 快速开发微信程序和第三方支付平台调试
- 本地WEB外网访问、本地开发微信、TCP端口转发
- 本站新增FRP服务器,基于 FRP 实现https、udp转发
- 无需任何配置,下载客户端之后直接一条命令让外网访问您的内网不再是距离
使用教程:https://www.ngrok.cc/_book/general/open.html
- 登录成功后可去隧道管理开通隧道
- 购买服务:服务是免费的,所有速度方面有点慢,但不影响测试
- 开通成功后前往隧道管理进行查看
- 下载客户端
- 根据需求选择客户端
- 下载之后得到两个文件,可以通过cmd命令行进到sunny.exe所在的目录执行
sunny.exe clientid 隧道id
这个网址代表的就是你本地项目的服务名和端口号
当内网穿透配置好后
支付接口中的 alipayRequest.setNotifyUrl("") 填写对应的网址就可以实现异步通知啦
3.3. 前端页面编写
前端需要导入二维码生成的组件
3.3.1. 二维码组件导入安装
npm install vue-qr --save
二维码样式设计参考:https://www.jianshu.com/p/ef5803f2b96b
3.3.2. 编写支付页面
<template>
<div id="orderDiv">
<el-button @click="toPayment" type="primary">去支付</el-button>
<el-dialog
title="支付宝扫码支付"
:visible.sync="dialogVisible"
width="252px"
center>
<!--二维码生成-->
<vueQr :text="qr" :size="200" :margin="10" :correctLevel="0" :whiteMargin="false" :logoSrc="this.$store.state.logoUrl"></vueQr>
<span id="dialogSpan" v-show="paySucc == true"></span>
<span id="dialogSpan2" v-show="paySucc == true">
<i class="el-icon-success" style="background-color: #FFFFFF; color:#67C23A; font-size: 60px;border-radius: 30px;"/>
<br/>
支付成功
</span>
</el-dialog>
</div>
</template>
<script>
import vueQr from 'vue-qr'
import axios from 'axios'
export default {
components:{
vueQr
},
data(){
return{
dialogVisible: false,
qr: "",
paySucc: false
}
},
methods: {
toPayment(){
let _this = this;
// 这里填写后台支付的地址
axios({
method: 'POST',
url: "/yixue/service-order/web/order/sandboxPay",
}).then(res => {
this.qr = res.data.data.qr;
if ("WebSocket" in window) {
// 打开一个 web socket
// 通道地址按照项目进行配置
var ws = new WebSocket("ws://localhost:8301/bindingRecord");
ws.onopen = function() {
// Web Socket 已连接上,使用 send() 方法发送数据
// ws.send("data");
// alert("数据发送中...");
};
ws.onmessage = function(evt) {
var received_msg = evt.data;
// 接收后台推送的数据
// alert("数据已接收..." + evt.data);
if (Boolean(evt.data)) {
_this.paySucc = true;
setTimeout(() => {
_this.dialogVisible = false;
}, 3 * 1000);
}
ws.close();
};
ws.onclose = function() {
};
} else {
// 浏览器不支持 WebSocket
alert("您的浏览器不支持 WebSocket!");
}
});
this.dialogVisible = true;
}
}
}
</script>
<style scoped>
#dialogSpan{
display: inline-block;
width: 200px;
height: 200px;
position: absolute;
top: 80px;
right: 27px;
opacity:0.9;
background-color: #FFFFFF;
}
#dialogSpan2{
display: inline-block;
width: 100px;
height: 100px;
position: absolute;
top: 124px;
right: 80px;
font-size: 20px;
text-align: center;
}
</style>
3.4. 结果测试
- 手机扫码
使用思路: 前端先创建websocket , 连接到后端websocket ,这样才能将websocket通道连接。当支付成功之后,后端向前端反馈支付成功信息,前端监控接收到消息后做处理,即关闭二维码对话框。
3.5. 注意要点
-
在后台接口进行配置的时候,一定要注意应用公钥和应用私钥是否可以成功配对,不然会报验签失败的错误,可去支付宝秘钥生成器进行检查
-
notifyUrl使用的url是外网地址,并不是IP地址,否则会无法回调。但是在学习环境下使用不了外网地址。这里我们使用 Sunny-Ngrok 内网穿透服务实现
-
支付成功后,回调函数执行了,如何告诉前端我已经支付成功,同时关闭扫描二维码按钮,这里就用到了websocket,这里不详细介绍websocket是什么,只要知道一点,它是可以监听到后端是否发送信息。
想详细了解的话,可以去菜鸟教程学习。
好啦!到了这一步相信你已经测试成功啦!后续项目上线根据需求修改就好
参考文档: https://blog.csdn.net/qq_30385099/article/details/109089450