Spring Boot + Vue实现支付宝扫码支付(沙箱环境)

1. 什么是支付宝扫码支付?

       现如今,手机支付已相当普遍,而作为开发人员应该对手机支付操作有所了解。而支付宝接口是支付宝提供的一个接口,用来对接软件应用程序在进行金钱交易使用。然后对于编程爱好者而言,想学习这一点就有点难,因为要想使用支付宝接口,必须前提是使用软件应用程序软件应用程序需要向支付宝申请,提交一系列资料,这一点是实现不了的。这就对开发者增加了一定的难度,因为产品没有上线,然后需要对接支付宝接口就是很大的问题,所以出现了沙箱环境,具有虚拟的用户和管理员账户,进行实验测试是否对接成功。

2. 使用技术 + 编程软件

  1. idea + SpringBoot
  2. vue + ElementUI + vue-Qr (vue生成二维码框架)
  3. Sunny-Ngrok (内网穿透服务,用于支付宝回调)
  4. WebSocket (实现前端响应)

3. 步骤

  1. 准备沙箱环境
  2. 后端接口配置
  3. 前端页面设计
  4. 结果测试
  5. 注意要点

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)
特点
  • 提供免费内网穿透服务,免费服务器支持绑定自定义域名
  • 管理内网服务器,内网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. 注意要点

  1. 在后台接口进行配置的时候,一定要注意应用公钥应用私钥是否可以成功配对,不然会报验签失败的错误,可去支付宝秘钥生成器进行检查

  2. notifyUrl使用的url是外网地址,并不是IP地址,否则会无法回调。但是在学习环境下使用不了外网地址。这里我们使用 Sunny-Ngrok 内网穿透服务实现

  3. 支付成功后,回调函数执行了,如何告诉前端我已经支付成功,同时关闭扫描二维码按钮,这里就用到了websocket,这里不详细介绍websocket是什么,只要知道一点,它是可以监听到后端是否发送信息。

    想详细了解的话,可以去菜鸟教程学习。

好啦!到了这一步相信你已经测试成功啦!后续项目上线根据需求修改就好

参考文档: https://blog.csdn.net/qq_30385099/article/details/109089450

  • 13
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 20
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值