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)測試是否對(duì)接成功。接下來就根據(jù)我的經(jīng)驗(yà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é)果測試
首先創(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ì)簡單介紹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ì)話框。
測試結(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-12ElementUI之表格toggleRowSelection選中踩坑記錄
這篇文章主要介紹了ElementUI之表格toggleRowSelection選中踩坑記錄,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03vue數(shù)據(jù)雙向綁定原理解析(get & set)
這篇文章主要為大家詳細(xì)解析了vue.js數(shù)據(jù)雙向綁定原理,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03Vue3路由進(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-03vue項(xiàng)目中在外部js文件中直接調(diào)用vue實(shí)例的方法比如說this
這篇文章主要介紹了vue項(xiàng)目中在外部js文件中直接調(diào)用vue實(shí)例的方法比如說this,需要的朋友可以參考下2019-04-04Vue實(shí)現(xiàn)路由跳轉(zhuǎn)和嵌套
本篇文章主要介紹了Vue實(shí)現(xiàn)路由跳轉(zhuǎn)和嵌套,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-06-06Vue3父子組件傳參有關(guān)sync修飾符的用法詳解
這篇文章主要給大家介紹關(guān)于前端Vue3父子組件傳參有關(guān)sync修飾符的用法詳細(xì)解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助2021-09-09