SpringBoot + vue 实现支付宝扫码支付功能

2022/4/9 6:22:37

本文主要是介绍SpringBoot + vue 实现支付宝扫码支付功能,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

简言:

    前端时间在自己做的小项目上添加了ZFB的支付功能,并且优化了网页版支付宝的扫码支付,使用的框架是Spring + SpringBoot + SpringMVC + Mybatis + VUE。

准备:

    首先需要到支付宝官网申请沙箱测试的资格:https://open.alipay.com/platform/home.htm

 

 

 

 

 

 

 

 

 点击 查看接入文档 根据自己的操作系统下载密钥生成器,生成应用私钥

 

 

 

步骤一:

    引入支付宝的Jar包

<!--    支付宝 jar-->
    <dependency>
      <groupId>com.alipay.sdk</groupId>
      <artifactId>alipay-sdk-java</artifactId>
      <version>4.22.67.ALL</version>
    </dependency>

步骤二:

    创建支付宝配置类

package org.lpy.config;

import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.internal.util.AlipaySignature;
import lombok.extern.log4j.Log4j2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * 支付宝接口配置类
 * @author 林草莓233
 * @since 2022/04/08
 */
@Log4j2
@Configuration
public class PayConfig {

    // 请填写您的AppId(必填)
    public static final String appID = "";
    //应用私钥,这里修改生成的私钥即可(必填)
    public static final String privateKey = "";
    //支付宝公钥,不是应用公钥!!!(必填)
    public static final String publicKey = "";
    //默认即可(必填)
    public static final String charset = "utf-8";
    //默认即可(必填)
    public static final String signType = "RSA2";

    @Bean
    public AlipayClient alipayClient(){
        //沙箱环境使用https://openapi.alipaydev.com/gateway.do,线上环境使用https://openapi.alipay.com/gateway.do
        return new DefaultAlipayClient("https://openapi.alipaydev.com/gateway.do", appID, privateKey, "json", charset, publicKey, signType);
    }

    /**
     * 验签,是否正确
     */
    public static boolean checkSign(HttpServletRequest request){
        Map<String, String[]> requestMap = request.getParameterMap();
        Map<String, String> paramsMap = new HashMap<>();
        requestMap.forEach((key, values) -> {
            StringBuilder str = new StringBuilder();
            for(String value : values) {
                str.append(value);
            }
            log.info("ZFB验签:" + key + "===>" + str);
            paramsMap.put(key, str.toString());
        });
        //调用SDK验证签名
        try {
            return AlipaySignature.rsaCheckV1(paramsMap, PayConfig.publicKey, PayConfig.charset, PayConfig.signType);
        } catch (AlipayApiException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            log.info("*********************验签失败********************");
            return false;
        }
    }
}

步骤三:

    创建WeSorcket类,用来通知前端支付状态

package org.lpy.util;

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;

@Component
@ServerEndpoint("/webSocket")
@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());
            }
        }
    }
}

步骤四:

    创建交易控制中心(AliPayHandler)

package org.lpy.handler;

import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.request.AlipayTradePrecreateRequest;
import com.alipay.api.response.AlipayTradePrecreateResponse;
import lombok.extern.slf4j.Slf4j;
import org.lpy.config.PayConfig;
import org.lpy.pojo.AliReturnPayBean;
import org.lpy.util.WebSocket;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;

/**
 * 支付交易控制中心
 * @author 林草莓233
 * @since 2022/04/08
 */
@Controller
@Slf4j
public class AliPayHandler {

    @Resource
    private AlipayClient alipayClient;
    @Resource
    private WebSocket webSocket;
    @Value("${company}")
    private String company;
    @Value("${timeout}")
    private String timeout;

    @RequestMapping("/createQR")
    @ResponseBody
    public String send(BigDecimal money,String title) throws AlipayApiException {
        AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest(); //创建API对应的request类
     //异步回调地址
        request.setNotifyUrl("http://127.0.0.1:8081/call");
        //同步回调地址
//  request.setReturnUrl("");
        request.setBizContent( "{"+
                "\"out_trade_no\":\""+ System.currentTimeMillis()/1000 + Math.round((Math.random()+1) * 1000) + "\"," + // 商户订单号
                "\"total_amount\":\""+ money +"\"," +// 商品价格
                "\"subject\":\""+ title +"\"," +// 商品标题
                "\"store_id\":\"" + company + "\"," +    // 组织或公司名
                "\"timeout_express\":\"" + timeout + "\"}" );    //支付超时时间
        AlipayTradePrecreateResponse response = alipayClient.execute(request);
        if (response.isSuccess()) {
            log.info("支付API调用成功");
            return response.getQrCode();
        } else {
            log.info("支付API调用失败");
        }
        return "";
    }

    // 支付宝回调函数
    @RequestMapping("/call")
    public void call(HttpServletRequest request, HttpServletResponse response, AliReturnPayBean returnPay) throws IOException {
        response.setContentType("type=text/html;charset=UTF-8");
        log.info("支付宝的的回调函数被调用");
        if (!PayConfig.checkSign(request)) {
            log.info("验签失败");
            response.getWriter().write("failture");
            return;
        }
        if (returnPay == null) {
            log.info("支付宝的returnPay返回为空");
            response.getWriter().write("success");
            return;
        }
        log.info("支付宝的returnPay" + returnPay);
        //表示支付成功状态下的操作
        if (returnPay.getTrade_status().equals("TRADE_SUCCESS")) {
            log.info("支付宝的支付状态为TRADE_SUCCESS");
            //业务逻辑处理 ,webSocket在下面会有介绍配置
            webSocket.sendMessage("true");
        }
        response.getWriter().write("success");
    }
}

这里要注意!!!!

request.setNotifyUrl("http://127.0.0.1:8081/call");

    这里的地址是错误的,这里应该要填写外网可以访问到的地址后面拼接上应用端口号加上“/call”,这样支付宝才能调用到我们的call方法,返回支付状态,这样因为隐私问题,我用内网的地址代替外网,如果同学们要部署在服务器上,这样应该填写服务器的公网IP加上"/call",例如你的公网IP为:123.45.6.7,应用端口号为:8081,回调地址应该填"http://123.45.6.7:8081/call",同样的,之前在支付宝沙箱页面也需要修改回调地址,两个保持一致。如果同学们要在本地测试,则需要通过内网穿透来实现支付宝的回调,我会把内网穿透的方法放在最后。

 

步骤五:

 

    前端页面,这里使用的是 VUE 框架 + element 组件 + qr 二维码生成组件

 

    先通过命令加载 qr 组件:

npm install vue-qr --save

 前端页面代码:

<template>
    <div>
        <!-- 支付按钮,模拟支付操作 -->
        <van-button type="primary" @click="pay">支付</van-button>

        <el-dialog :title="paySucc?'支付成功':'扫码支付'" :visible.sync="dialogVisible" width="16%" center>
            <!-- 生成二维码图片 -->
            <vueQr :text="text" :size="200" v-if="!paySucc"></vueQr>
            <!-- 使用websocket监控是否扫描,扫描成功显示成功并退出界面 -->
            <span class="iconfont icon-success" style="position: relative;font-size: 100px;color:#42B983;margin-left: 50px;top:-10px;" v-else></span>
        </el-dialog>

    </div>
</template>

<script>
    import vueQr from 'vue-qr'
    export default {
        data() {
            return {
                dialogVisible: false,
                text: "",
                paySucc: false
            }
        },
        components: {
            vueQr
        },
        methods: {
            pay() {
                let _this = this;
                _this.paySucc = false;
                _this.dialogVisible = true;
                this.axios.request("http://localhost:8081/createQR")
                    .then((response) => {
                        _this.text = response.data;
                        _this.dialogVisible = true;
                        //使用webSocket发送请求,下面会简单介绍websocket使用
                        if ("WebSocket" in window) {
                            // 打开一个 web socket
                            var ws = new WebSocket("ws://localhost:8081/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() {
                                // // 关闭 websocket
                                console.log("连接已关闭...");
                            };
                        } else {
                            // 浏览器不支持 WebSocket
                            alert("您的浏览器不支持 WebSocket!");
                        }
                    }).catch((err) => {
                        console.log(err)
                    })
            },
            back(dataUrl, id) {
                console.log(dataUrl, id)
            }
        }
    }
</script>

<style>
    .btn {
        margin-left: 100px;
    }
</style>

示例:

附言:

    实现内网穿透我们需要用到专门的工具,这里有两种,分别是 Sunny-Ngrok 和 NATAPP 这两个软件都有免费通道和付费通道,免费的通道不稳定,而且每次开启地址都会变,但如果只是测试可以凑合着用。

百度搜索NATAPP官网,进去注册领取免费的隧道,然后配置。

 

 

 

 点击下载客户端,下载对应系统的natapp.exe文件,然后在natapp.exe文件同目录下创建config.ini文件,编辑文件内容

[default]
authtoken=                      #对应一条隧道的authtoken
clienttoken=                    #对应客户端的clienttoken,将会忽略authtoken,若无请留空,
log=none                        #log 日志文件,可指定本地文件, none=不做记录,stdout=直接屏幕输出 ,默认为none
loglevel=ERROR                  #日志等级 DEBUG, INFO, WARNING, ERROR 默认为 DEBUG
http_proxy=                     #代理设置 如 http://10.123.10.10:3128 非代理上网用户请务必留空

只需要在authtoken= 后面填上你注册的隧道的authtoken码保存,之后直接打开natapp.exe即可完成内网穿透

 



这篇关于SpringBoot + vue 实现支付宝扫码支付功能的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程