后端
1、pom文件
- 引入WebSocket包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
- 引入支付宝sdk
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.22.67.ALL</version>
</dependency>
2、application.yml
AliPay:
# 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号,在沙箱应用中获取
appId:
# 商户私钥,您的PKCS8格式RSA2私钥,通过开发助手生成的应用私钥
privateKey:
# 支付宝公钥,在沙箱应用获取,通过应用公钥生成支付宝公钥
publicKey:
# 服务器异步通知页面路径需http://格式的完整路径,测试环境需要内网穿透natapp
notifyUrl: http://cd8apv.natappfree.cc/alipay/call
# 页面跳转同步通知页面路径 需http://格式的完整路径,测试环境需要内网穿透natapp
returnUrl: http://cd8apv.natappfree.cc/alipay/call
# 签名方式
signType: RSA2
# 字符编码格式
charset: utf-8
# 支付宝网关,在沙箱应用中获取
gatewayUrl: https://openapi.alipaydev.com/gateway.do
3、创建AlipayController
package com.think.controller;
import com.alibaba.druid.util.StringUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.request.AlipayTradePrecreateRequest;
import com.alipay.api.response.AlipayTradePrecreateResponse;
import com.think.pojo.AliReturnPay;
import com.think.util.ShareFuncUtil;
import com.think.util.WebSocket;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
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.util.*;
/**
* @Program: IntelliJ IDEA
* @Description: AlipayController
* @Author: Li
* @CreateDate: 2022-08-22 09:56
* @Version: 1.1.0
**/
@Slf4j
@Controller
@RequestMapping("/alipay")
public class AlipayController {
//获取配置文件中的配置信息
//应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
@Value("${AliPay.appId}")
private String appId;
//商户私钥 您的PKCS8格式RSA2私钥
@Value("${AliPay.privateKey}")
private String privateKey;
//支付宝公钥
@Value("${AliPay.publicKey}")
private String publicKey;
//服务器异步通知页面路径
@Value("${AliPay.notifyUrl}")
private String notifyUrl;
//页面跳转同步通知页面路径
@Value("${AliPay.returnUrl}")
private String returnUrl;
//签名方式
@Value("${AliPay.signType}")
private String signType;
//字符编码格式
@Value("${AliPay.charset}")
private String charset;
//支付宝网关
@Value("${AliPay.gatewayUrl}")
private String gatewayUrl;
private final String format = "json";
@Autowired
private WebSocket webSocket;
@PostMapping("/sandboxPay")
@ResponseBody
public Object sandboxPay(AliReturnPay aliReturnPay) throws AlipayApiException{
AlipayClient alipayClient = new DefaultAlipayClient(gatewayUrl,appId,privateKey,format,charset,publicKey,signType);
AlipayTradePrecreateRequest alipayRequest = new AlipayTradePrecreateRequest();
// 设置支付宝异步通知回调地址 (注意:这个网址必须是可以通过外网访问的网址)
alipayRequest.setNotifyUrl(returnUrl);
alipayRequest.setReturnUrl(returnUrl);
// 订单信息
aliReturnPay.setOut_trade_no(UUID.randomUUID().toString().replaceAll("-",""));
aliReturnPay.setSubject("订单名称");
aliReturnPay.setTotal_amount(String.valueOf(10));
aliReturnPay.setBody("商品描述");
aliReturnPay.setStore_id("公司名");
aliReturnPay.setTimeout_express("90m");
alipayRequest.setBizContent (JSON.toJSONString(aliReturnPay));
AlipayTradePrecreateResponse response = alipayClient.execute (alipayRequest);
// 返回支付宝支付网址,用于生成二维码
return ShareFuncUtil.returnObject(response.getQrCode(),aliReturnPay);
}
@RequestMapping("/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(JSON.toJSONString(aliReturnPay));
}
}
}
4、创建WebSocket类
package com.think.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;
/**
* @Program: IntelliJ IDEA
* @Description: WebSocket
* @Author: Li
* @CreateDate: 2022-08-24 14:58
* @Version: 1.1.0
**/
@ServerEndpoint("/webSocket")
@Component
@Slf4j
public class WebSocket {
private Session session;
public 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());
}
}
}
}
5、创建AliReturnPay Bean
package com.think.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serializable;
/**
* @Program: IntelliJ IDEA
* @Description: AliReturnPay\
* @Author: Li
* @CreateDate: 2022-08-24 14:55
* @Version: 1.1.0
**/
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class AliReturnPay implements Serializable {
private static final long serialVersionUID = 8683949075381016639L;
private String id;
// 开发者的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;
//用户
private String user_tel;
//ip
private String host_ip;
//外网ip
private String external_ip;
//订单名称
private String subject;
//订单内容
private String body;
//
private String store_id;
private String timeout_express;
}
6、Controller 的 return 方法 ShareFuncUtil 类
package com.think.util;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
public class ShareFuncUtil {
public static Object returnObject(Object object) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", 200);
jsonObject.put("data", object);
return jsonObject;
}
public static Object returnObject(Object object,Object object2) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", 200);
jsonObject.put("data1", object);
jsonObject.put("data2", object2);
return jsonObject;
}
public static Object returnNotObject(int code) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", code);
return jsonObject;
}
public static void writerPring(HttpServletRequest requests, HttpServletResponse responses, ServletResponse response, int code) throws IOException {
responses.setHeader("Access-Control-Allow-Origin", requests.getHeader("Origin"));
responses.setHeader("Access-Control-Allow-Credentials", "true");
responses.setContentType("application/json; charset=utf-8");
responses.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
out.print(ShareFuncUtil.returnNotObject(code));
out.flush();
out.close();
}
private static ArrayList<String> arrList = new ArrayList<>(Arrays.asList("q","w","e","r","t","y","u","i","o","p","a","s","d","f","g","h","j","k","l","z","x","c","v","b","n","m","Q","W","E","R","T","Y","U","I","O","P","A","S","D","F","G","H","J","K","L","Z","X","C","V","B","N","M","0","1","2","3","4","5","6","7","8","9"));
/** 生成邀请码
* @param num 位数
* @return 邀请码
*/
public static String makeRandomArr(int num){
if(num >= arrList.size()){
return "";
}
Random random = new Random();
int temRandomNum = 0;
ArrayList<String> newArrList = new ArrayList<>();
for(int i = 0; i < num; i++){
temRandomNum = random.nextInt(arrList.size()-1);
newArrList.add(arrList.get(temRandomNum));
}
return newArrList.toString().replace("[", "").replace("]", "").replaceAll(", ", "");
}
}
7、NATAPP 内网穿透
-
进入官网点击右上角注册
-
注册登录后一系列信息填写完成后点击左上角购买隧道 免费的就行
-
填写信息
-
购买完成后下载对应自己的客户端
-
在NATAPP同级目录创建 config.ini
#将本文件放置于natapp同级目录 程序将读取 [default] 段
#在命令行参数模式如 natapp -authtoken=xxx 等相同参数将会覆盖掉此配置
#命令行参数 -config= 可以指定任意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 粘贴到 config.ini 文件里面,只需要填写 authtoken ,其他不用填
- 双击 natapp.exe 运行
此处的 http://cd8apv.natappfree.cc 就代表 127.0.0.1:8080
如:需要支付成功需要访问 AlipayController 里面的 call 方法
则:http://127.0.0.1:8080/alipay/call
换:http://cd8apv.natappfree.cc/alipay/call
就可以访问到
前端
安装vue-qr
npm i vue-qr -s
引入
import vueQr from 'vue-qr';
定义组件
components: {
vueQr,
}
HTML标签
<vueQr :text="qr" :size="200" :margin="10" :correctLevel="0" :whiteMargin="false" :logoSrc="logoSrc" :logoMargin="5"></vueQr>
- 封装的 axios 方法 正常请求就行
- 请求 sandboxPay 方法
sandboxPay().then(res => {
//返回的二维码数据
this.qr = res.data1;
//返回的支付信息
this.orderInfo = res.data2;
if ('WebSocket' in window) {
// 打开一个 web socket
// 使用store防止多次创建连接
let ws = this.$store.state.app.webSocket;
if(!ws){
// 新建webSocket连接
ws = new WebSocket('ws://localhost:8080/webSocket');
this.$store.commit('app/SET_WEBSOCKET', ws);
}
ws.onopen = function() {
// Web Socket 已连接上,使用 send() 方法发送数据
// alert('数据发送中...');
};
ws.onmessage = function(evt) {
var data = JSON.parse(evt.data);
// 接收后台推送的数据
if (data.trade_status == 'TRADE_SUCCESS') {
console.log('支付成功');
_this.paySucc = true;
// 这里支付成功操作业务
}
ws.close();
_this.$store.commit('app/SET_WEBSOCKET', null);
};
ws.onclose = function() {
ws.close();
_this.$store.commit('app/SET_WEBSOCKET', null);
};
} else {
// 浏览器不支持 WebSocket
alert('您的浏览器不支持 WebSocket!');
}
});