springboot+vue+對(duì)接支付寶接口+二維碼掃描支付功能(沙箱環(huán)境)
1. 什么是支付寶接口(沙箱環(huán)境)?
記錄時(shí)間:2020年10月15日3:55
現(xiàn)如今,手機(jī)支付已相當(dāng)普遍,而作為開發(fā)人員應(yīng)該對(duì)手機(jī)支付操作有所了解。而支付寶接口是支付寶提供的一個(gè)接口,用來對(duì)接軟件應(yīng)用程序在進(jìn)行金錢交易使用。然后對(duì)于編程愛好者而言,想學(xué)習(xí)這一點(diǎn)就有點(diǎn)難,因?yàn)橐胧褂弥Ц秾毥涌?,必須前提是使用軟件?yīng)用程序,軟件應(yīng)用程序需要向支付寶申請(qǐng),提交一系列資料,這一點(diǎn)是實(shí)現(xiàn)不了的。這就對(duì)開發(fā)者增加了一定的難度,因?yàn)楫a(chǎn)品沒有上線,然后需要對(duì)接支付寶接口就是很大的問題,所以出現(xiàn)了沙箱環(huán)境,具有虛擬的用戶和管理員賬戶,進(jìn)行實(shí)驗(yàn)測(cè)試是否對(duì)接成功。接下來就根據(jù)我的經(jīng)驗(yàn),簡(jiǎn)單的介紹一下我的使用和學(xué)習(xí)過程。
使用技術(shù)+編程軟件:
springboot(idea)vue + elementui(HBuilderX)+ vue-qr(vue生成二維碼框架)NATAPP(連接外網(wǎng),實(shí)現(xiàn)支付寶回調(diào))websocket(實(shí)現(xiàn)前端響應(yīng))
步驟:
準(zhǔn)備沙箱環(huán)境JAVA + springboot 中使用 SDK 連接支付寶接口配置前端使用vue+elementui頁面設(shè)計(jì)需要注意點(diǎn)結(jié)果測(cè)試
首先創(chuàng)建應(yīng)用,這里是沙箱環(huán)境,先準(zhǔn)備沙箱環(huán)境:
百度搜索**支付寶螞蟻金服**,登錄支付包賬號(hào),出現(xiàn)如下顯示:

這里是創(chuàng)建應(yīng)用的地方,也就是說有項(xiàng)目要上線時(shí),在這里申請(qǐng)。使用沙箱環(huán)境的話,點(diǎn)擊左上角開放平臺(tái),然后往下拉,會(huì)出現(xiàn)沙箱二字,點(diǎn)擊進(jìn)入即可:

然后就可以看到支付寶官網(wǎng)介紹的沙箱環(huán)境的使用,其實(shí)官網(wǎng)已經(jīng)介紹的非常詳細(xì)了,如有小伙伴們看著嫌麻煩,可以根據(jù)我學(xué)習(xí)的步驟作為參考。在如何使用沙箱環(huán)境下點(diǎn)擊沙箱應(yīng)用鏈接,去配置自己的沙箱環(huán)境。

點(diǎn)擊 i 符號(hào),上面有提示鏈接,如何生成RSA2密鑰,這里就不詳細(xì)介紹了,官網(wǎng)寫的非常明白,我這么笨的人也可以學(xué)會(huì),相信小伙伴們也可以學(xué)會(huì)的。按照步驟會(huì)生成兩個(gè)文件:應(yīng)用公鑰和應(yīng)用私鑰。記住,是應(yīng)用公鑰,而不是支付寶公鑰。這里學(xué)習(xí)過程中沒有使用證書模式。

然后點(diǎn)擊RSA2的設(shè)置/查看。將產(chǎn)生的應(yīng)用公鑰復(fù)制進(jìn)去就可以了,而下面會(huì)生成支付寶公鑰,在后續(xù)連接過程中非常重要,需要區(qū)分使用的是支付寶公鑰還是應(yīng)用公鑰。

JAVA + springboot 中使用 SDK 連接支付寶接口配置
這里是官網(wǎng)使用SDK的方法,這里使用舊版的方式,新版的方式我使用過程中出現(xiàn)某些問題不會(huì)解決。
使用之前,先封裝幾個(gè)配置,第一個(gè)是連接支付寶的配置:
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.internal.util.AlipaySignature;
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;
/**
* 支付寶接口配置類
*/
@Configuration
public class PayConfig {
// 請(qǐng)?zhí)顚懩腁ppId,例如:2019091767145019(必填)
private static final String appID = "2016102500758313";
//應(yīng)用私鑰,這里修改生成的私鑰即可(必填)
private static final String privateKey = "";
//支付寶公鑰,而非應(yīng)用公鑰(必填)
public static final String publicKey = "";
//默認(rèn)即可(必填)
public static final String charset = "utf-8";
//默認(rèn)即可(必填)
public static final String signType = "RSA2";
@Bean
public AlipayClient alipayClient(){
//沙箱環(huán)境使用https://openapi.alipaydev.com/gateway.do,線上環(huán)境使用https://openapi.alipay.com/gateway.do
return new DefaultAlipayClient("https://openapi.alipaydev.com/gateway.do", appID, privateKey, "json", charset, publicKey, signType);
}
/**
* 驗(yàn)簽,是否正確
*/
public static boolean checkSign(HttpServletRequest request){
Map<String, String[]> requestMap = request.getParameterMap();
Map<String, String> paramsMap = new HashMap<>();
requestMap.forEach((key, values) -> {
String strs = "";
for(String value : values) {
strs = strs + value;
}
System.out.println(key +"===>"+strs);
paramsMap.put(key, strs);
});
System.out.println();
//調(diào)用SDK驗(yàn)證簽名
try {
return AlipaySignature.rsaCheckV1(paramsMap, PayConfig.publicKey, PayConfig.charset, PayConfig.signType);
} catch (AlipayApiException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("*********************驗(yàn)簽失敗********************");
return false;
}
}
}
然后封裝一個(gè)支付寶回調(diào)的參數(shù)對(duì)象,這里就不需要自己手動(dòng)去獲取參數(shù)了
import java.io.Serializable;
/**
* 支付寶回調(diào)參數(shù)
*/
public class AliReturnPayBean implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* 開發(fā)者的app_id
*/
private String app_id;
/**
* 商戶訂單號(hào)
*/
private String out_trade_no;
/**
* 簽名
*/
private String sign;
/**
* 交易狀態(tài)
*/
private String trade_status;
/**
* 支付寶交易號(hào)
*/
private String trade_no;
/**
* 交易的金額
*/
private String total_amount;
public String getTotal_amount() {
return total_amount;
}
public void setTotal_amount(String total_amount) {
this.total_amount = total_amount;
}
public String getApp_id() {
return app_id;
}
public void setApp_id(String app_id) {
this.app_id = app_id;
}
public String getOut_trade_no() {
return out_trade_no;
}
public void setOut_trade_no(String out_trade_no) {
this.out_trade_no = out_trade_no;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public String getTrade_status() {
return trade_status;
}
public void setTrade_status(String trade_status) {
this.trade_status = trade_status;
}
public String getTrade_no() {
return trade_no;
}
public void setTrade_no(String trade_no) {
this.trade_no = trade_no;
}
@Override
public String toString() {
return "AliReturnPayBean [app_id=" + app_id + ", out_trade_no=" + out_trade_no + ", sign=" + sign
+ ", trade_status=" + trade_status + ", trade_no=" + trade_no + "]";
}
}
然后寫一個(gè)控制層去連接支付寶,控制層必須是@Controller修飾,而不是@RestController修飾,因?yàn)橹Ц秾毜幕卣{(diào)函數(shù)里面返回的是請(qǐng)求。具體事例如下:
前提:在pom.xml 中導(dǎo)入SDK依賴:
<dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>4.10.145.ALL</version> </dependency>
package com.example.zhifubaozhifu.controller;
import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.request.AlipayTradePrecreateRequest;
import com.alipay.api.response.AlipayTradePrecreateResponse;
import com.example.zhifubaozhifu.config.PayConfig;
import com.example.zhifubaozhifu.util.AliReturnPayBean;
import com.example.zhifubaozhifu.util.Shop;
import com.example.zhifubaozhifu.util.WebSocket;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
@Controller
@Slf4j
public class Test {
@Autowired
private AlipayClient alipayClient;
@Autowired
private WebSocket webSocket;
@RequestMapping("/createQR")
@ResponseBody
public String send() throws AlipayApiException {
AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest(); //創(chuàng)建API對(duì)應(yīng)的request類
// 在下面會(huì)介紹notifyUrl怎么來的
request.setNotifyUrl("http://cv95x3.natappfree.cc/call");
//同步回調(diào)地址
// request.setReturnUrl("");
request.setBizContent(" {" +
" \"primary_industry_name\":\"IT科技/IT軟件與服務(wù)\"," +
" \"primary_industry_code\":\"10001/20102\"," +
" \"secondary_industry_code\":\"10001/20102\"," +
" \"secondary_industry_name\":\"IT科技/IT軟件與服務(wù)\"" +
" }");;
AlipayTradePrecreateResponse response = alipayClient.execute(request);
String path = "zhifu.jpg";
if (response.isSuccess()) {
System.out.println("調(diào)用成功");
return response.getQrCode();
} else {
System.out.println("調(diào)用失敗");
}
return "";
}
/**
* 支付寶回調(diào)函數(shù)
* @param request
* @param response
* @param returnPay
* @throws IOException
*/
@RequestMapping("/call")
public void call(HttpServletRequest request, HttpServletResponse response, AliReturnPayBean returnPay) throws IOException {
response.setContentType("type=text/html;charset=UTF-8");
log.info("支付寶的的回調(diào)函數(shù)被調(diào)用");
if (!PayConfig.checkSign(request)) {
log.info("驗(yàn)簽失敗");
response.getWriter().write("failture");
return;
}
if (returnPay == null) {
log.info("支付寶的returnPay返回為空");
response.getWriter().write("success");
return;
}
log.info("支付寶的returnPay" + returnPay.toString());
//表示支付成功狀態(tài)下的操作
if (returnPay.getTrade_status().equals("TRADE_SUCCESS")) {
log.info("支付寶的支付狀態(tài)為TRADE_SUCCESS");
//業(yè)務(wù)邏輯處理 ,webSocket在下面會(huì)有介紹配置
webSocket.sendMessage("true");
}
response.getWriter().write("success");
}
}
前端使用vue+elementui頁面設(shè)計(jì):
vue項(xiàng)目的搭建這里就不介紹,首先準(zhǔn)備環(huán)境,添加 vue-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監(jiān)控是否掃描,掃描成功顯示成功并退出界面 -->
<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發(fā)送請(qǐng)求,下面會(huì)簡(jiǎn)單介紹websocket使用
if ("WebSocket" in window) {
// 打開一個(gè) web socket
var ws = new WebSocket("ws://localhost:8081/bindingRecord");
ws.onopen = function() {
// Web Socket 已連接上,使用 send() 方法發(fā)送數(shù)據(jù)
// ws.send("data");
// alert("數(shù)據(jù)發(fā)送中...");
};
ws.onmessage = function(evt) {
var received_msg = evt.data;
// alert("數(shù)據(jù)已接收..." + evt.data);
if (Boolean(evt.data)) {
_this.paySucc = true;
setTimeout(() => {
_this.dialogVisible = false;
}, 3 * 1000);
}
ws.close();
};
ws.onclose = function() {
// // 關(guān)閉 websocket
console.log("連接已關(guān)閉...");
};
} else {
// 瀏覽器不支持 WebSocket
alert("您的瀏覽器不支持 WebSocket!");
}
}).catch((err) => {
console.log(err)
})
},
back(dataUrl, id) {
console.log(dataUrl, id)
}
}
}
</script>
<style>
.btn {
margin-left: 100px;
}
</style>
這樣前后端代碼就準(zhǔn)備就緒了,再上面的代碼中有兩個(gè)需要注意的: 第一個(gè)問題是:notifyUrl使用的url是外網(wǎng)地址,并不是IP地址,否則會(huì)無法回調(diào)。但是在學(xué)習(xí)環(huán)境下使用不了外網(wǎng)地址。這就需要使用**NATAPP**。百度搜索NATAPp官網(wǎng),點(diǎn)進(jìn)去下載,它是一個(gè)natapp.exe 應(yīng)用,這有這個(gè)。在官網(wǎng)注冊(cè)賬號(hào),點(diǎn)擊購買免費(fèi)隧道(免費(fèi)隧道只能購買兩個(gè),要珍惜哦。隧道協(xié)議寫web,其他的就按要本地環(huán)境配置就可以了)。購買之后就會(huì)有下面一條自己的隧道

雙擊natapp.exe ,進(jìn)入控制臺(tái),輸入以下命令,開啟隧道:
# 這里的值是上面的authtoken的值 natapp --authtoken=值
之后如下顯示:

這里的外網(wǎng)連接地址,就是notifyUrl的地址,然后再加上方法mapping路徑即可,如我的是:http://cv95x3.natappfree.cc/call
第二個(gè)問題是:支付成功后,回調(diào)函數(shù)執(zhí)行了,如何告訴前端我已經(jīng)支付成功,同時(shí)關(guān)閉掃描二維碼按鈕,這里就用到了websocket,這里不詳細(xì)介紹websocket是什么,只要知道一點(diǎn),它是可以監(jiān)聽到后端是否發(fā)送信息。首先在pom.xml中導(dǎo)入依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> <version>2.3.4.RELEASE</version> </dependency>
然后創(chuàng)建一個(gè)webconfig的配置類:
package com.example.zhifubaozhifu.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;
/**
* @desc: WebSocket服務(wù)
*
**/
//連接webSocket服務(wù)的URL
@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("【新建連接】,連接總數(shù):{}", webSockets.size());
}
/**
* 斷開連接
*/
@OnClose
public void onClose(){
webSockets.remove(this);
log.info("【斷開連接】,連接總數(shù):{}", webSockets.size());
}
/**
* 接收到信息
* @param message
*/
@OnMessage
public void onMessage(String message){
log.info("【收到】,客戶端的信息:{},連接總數(shù):{}", message, webSockets.size());
}
/**
* 發(fā)送消息
* @param message
*/
public void sendMessage(String message){
log.info("【廣播發(fā)送】,信息:{},總連接數(shù):{}", message, webSockets.size());
for (WebSocket webSocket : webSockets) {
try {
webSocket.session.getBasicRemote().sendText(message);
} catch (IOException e) {
log.info("【廣播發(fā)送】,信息異常:{}", e.fillInStackTrace());
}
}
}
}
然后使用的時(shí)候調(diào)用方法onMessage即可接收消息,使用onMessage即可廣發(fā)消息。
前端使用步驟:
<script type="text/javascript">
function WebSocketTest()
{
if ("WebSocket" in window)
{
alert("您的瀏覽器支持 WebSocket!");
// 打開一個(gè) web socket
var ws = new WebSocket("ws://localhost:9998/echo");
ws.onopen = function()
{
// Web Socket 已連接上,使用 send() 方法發(fā)送數(shù)據(jù)
ws.send("發(fā)送數(shù)據(jù)");
alert("數(shù)據(jù)發(fā)送中...");
};
ws.onmessage = function (evt)
{
var received_msg = evt.data;
alert("數(shù)據(jù)已接收...");
};
ws.onclose = function()
{
// 關(guān)閉 websocket
alert("連接已關(guān)閉...");
};
}
else
{
// 瀏覽器不支持 WebSocket
alert("您的瀏覽器不支持 WebSocket!");
}
}
</script>
想詳細(xì)了解的話,可以去菜鳥教程學(xué)習(xí)。
使用思路: 前端先創(chuàng)建websocket , 連接到后端websocket ,這樣才能將websocket通道連接。當(dāng)支付成功之后,后端向前端反饋支付成功信息,前端監(jiān)控接收到消息后做處理,即關(guān)閉二維碼對(duì)話框。
測(cè)試結(jié)果:

然后下載支付寶沙箱版手機(jī)即可掃描模擬支付,在螞蟻金服的沙箱環(huán)境中就有二維碼下載,如下圖:

這里指記錄了支付到回調(diào)處理的操作,再這之上還可進(jìn)行進(jìn)一步的封裝。
搞定,記錄結(jié)束。
結(jié)束時(shí)間:2020年10月15日6:12
到此這篇關(guān)于springboot+vue+對(duì)接支付寶接口+二維碼掃描支付(沙箱環(huán)境)的文章就介紹到這了,更多相關(guān)springboot對(duì)接支付寶支付接口內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue實(shí)現(xiàn)商城秒殺倒計(jì)時(shí)功能
這篇文章主要介紹了vue實(shí)現(xiàn)商城秒殺倒計(jì)時(shí)功能,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-12-12
ElementUI之表格toggleRowSelection選中踩坑記錄
這篇文章主要介紹了ElementUI之表格toggleRowSelection選中踩坑記錄,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
vue數(shù)據(jù)雙向綁定原理解析(get & set)
這篇文章主要為大家詳細(xì)解析了vue.js數(shù)據(jù)雙向綁定原理,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03
Vue3路由進(jìn)階實(shí)戰(zhàn)教程之參數(shù)傳遞與導(dǎo)航守衛(wèi)核心技術(shù)
本文介紹了路由參數(shù)傳遞的進(jìn)階應(yīng)用技巧,包括路由配置與參數(shù)驗(yàn)證、組件參數(shù)接收、查詢參數(shù)傳遞、導(dǎo)航守衛(wèi)以及性能優(yōu)化與最佳實(shí)踐,感興趣的朋友一起看看吧2025-03-03
vue項(xiàng)目中在外部js文件中直接調(diào)用vue實(shí)例的方法比如說this
這篇文章主要介紹了vue項(xiàng)目中在外部js文件中直接調(diào)用vue實(shí)例的方法比如說this,需要的朋友可以參考下2019-04-04
Vue實(shí)現(xiàn)路由跳轉(zhuǎn)和嵌套
本篇文章主要介紹了Vue實(shí)現(xiàn)路由跳轉(zhuǎn)和嵌套,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-06-06
Vue3父子組件傳參有關(guān)sync修飾符的用法詳解
這篇文章主要給大家介紹關(guān)于前端Vue3父子組件傳參有關(guān)sync修飾符的用法詳細(xì)解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助2021-09-09

