Pass Assistant v3.0版本大更新说明+虎皮椒支付接入

在加上ai的神辅助下整蛊了一周多的代码才写出来的3.0大更新,期间包括破解了cursor的时间 (:doge)

整体页面大更新,让你的眼睛看起来更舒服了

gitee: Pass Assistant前端: 何平安魔改time sea后的gpt前端部分

Pass Assistant后端: 基于time sea修改后的gpt

更新内容

图床模块优化更新

新增了公共图床,可以将自己的图片设为私有或公有,共有任何人都可以查看,布局也比以前的无css叙利亚风更好看,站长也更有钱了,普通用户上限20张了(原来10张)后续3.1或3.2将添加用IT币来扩容图床

外联导航模块

反正就是看起来更舒服了页面,以前的显示还有bug

新增”关于我”模块

就是如图,一个非常简单的页面,但是使用了新技术lottie动态图,里面还有我的岁月史书~

还有许多细处的更新,反正就让这个网站看起来像人做的了

虎皮椒支付接入

因为平安我没法注册微信或支付宝支付商家(学生党懂得都懂),发现了三方支付代理,就选了虎皮椒支付(花了88大洋开户了支付宝,因为就在它就在重庆开的公司跑路了我也可以线下单杀),下面是我在我网站接入的java代码理解,全是自己手敲的ai都写不出来。

官网:虎皮椒-个人支付微信支付宝个人支付接口,个人网站收款API接口

官方api接口

支付网关接口URL:

1
https://api.xunhupay.com/payment/do.html  (设置为可配置的变量,以便接口变更时方便修改)

传参方式:Post

使用curl的post方式传参数,并直接获取json返回值,引导客户跳转到支付链接。

# 参数名 含义 类型 说明
1 version API 版本号 string(24) 必填。目前为1.1
2 appid APP ID string(32) 必填。填写虎皮椒的APPID,不是小程序APPID
3 trade_order_id 商户订单号 string(32) 必填。请确保在当前网站内是唯一订单号,只能是数字、大小写字母_-*
4 total_fee 订单金额(元) decimal(18,2) 必填。单位为人民币 元,没小数位不用强制保留2位小数
5 title 订单标题 string(128) 必填。商户订单标题(不能超过127个字符或者42个汉字,不能有表情符号)
6 time 当前时间戳 int(11) 必填。PHP示例:time()
7 notify_url 通知回调网址 string(128) 必填。用户支付成功后,我们服务器会主动发送一个post消息到这个网址(注意:当前接口内,SESSION内容无效,手机端不支持中文域名)
8 return_url 跳转网址 string(128) 可选。用户支付成功后,我们会让用户浏览器自动跳转到这个网址
9 callback_url 商品网址 string(128) 可选。用户取消支付后,我们可能引导用户跳转到这个网址上重新进行支付
10 plugins 名称 string(128) 可选。 用于识别对接程序或作者
11 attach 备注 text 可选。备注字段,可以传入一些备注数据,回调时原样返回
12 nonce_str 随机值 string(32) 必填。作用:1.避免服务器页面缓存,2.防止安全密钥被猜测出来
13 hash 签名 string(32) 必填。
14 type 支付通道类型 string(32) 微信H5支付请填”WAP”,微信小程序支付请填”JSAPI” ,请参考小程序demo对接小程序支付,微信内支付请勿填写”JSAPI”,支付网关为:https://api.xunhupay.com 跳转小程序APPID:wx2574b5c5ee8da56b,其他支付网关跳转小程序APPID:wx402faa5bd5eda155,(支付宝不需要此参数)
15 wap_url 网站域名 string(50) 网站域名,H5支付通道请填你网站域名,小程序支付请填支付网关(例如:https://api.dpweixin.com)。(支付宝不需要此参数)
16 wap_name 网站名称 string(50) 店铺名称或网站域名,长度32或以内,H5支付通道请求必填。(支付宝不需要此参数)

请求返回:

# 参数名 含义 类型 说明
1 oderid 订单id int 订单id(此处有个历史遗留错误,返回名称是openid,值是orderid,一般对接不需要这个参数)
2 url_qrcode 二维码地址(PC端使用) string(156) 二维码有效期五分钟,PC端可将该参数展示出来进行扫码支付,不用再转二维码,需自己处理跳转
3 url 请求url(手机端专用,PC端已停用) string(155) 只需跳转此参数即可,系统会自动判断是微信端还是手机端,自动返回return_url,不能先显示“url_qrcode”二维码,再跳转“url”链接
4 errcode 错误码 int
5 errmsg 错误信息 string(8) 错误信息具体值
6 hash 签名 string(32) 数据签名,参考下面签名算法

HASH生成的步骤如下:

第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。

特别注意以下重要规则:

  1. 参数名ASCII码从小到大排序(字典序);
  2. 如果参数的值为空不参与签名;
  3. 参数名区分大小写;
  4. 验证调用返回或微信主动通知签名时,传送的hash参数不参与签名,将生成的签名与该hash值作校验。
  5. 微信接口可能增加字段,验证签名时必须支持增加的扩展字段

第二步,在stringA最后拼接上APPSECRET(秘钥)得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,得到hash值(32位小写)。

知道你们喜欢cv大法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
@Test
public void pay(){
// appid
String appid = "";
// appsecret
String appsecret = "";
// 请求路径
String url = "https://api.xunhupay.com/payment/do.html";
// 设置 传递参数的集合,方便 传递数据。
Map<String,Object> options = new HashMap<>();
// 必填 设置版本号
options.put("version","1.1");
// 必填 设置 appid
options.put("appid",appid);
// 密钥不需要直接传递
//options.put("appsecret",appsecret);
// 必填 订单号 具体内容自己控制 长度 32位 官网说 请确保在当前网站内是唯一订单号,具体含义 我测试了 在描述 此备注
options.put("trade_order_id","1234567890");
// 必填 价格 精确到RMB分
options.put("total_fee","0.01");
// 必填 标题
options.put("title","Pass Assistant");
// 必填 当前时间戳 调用 刚写的方法 getSecondTimestamp
options.put("time", getSecondTimestamp(new Date()));
// 必填 通知回调地址 url 什么含义 我们后台需要知道 用户支付了。
options.put("notify_url","http://127.0.0.1:8601/pay/paycallback"); // 只有这个有用
// 非必填 使用 响应字段中 url 就直接跳到百度了,如果访问,url_qrcode ,不会直接跳转,只有当支付完成后,再次刷新 url_qrcode中的连接,才会跳转。
options.put("return_url","https://www.baidu.com");
// 非必填 用户取消支付,跳转的页面 经过测试,没有触发机制,建议不传递
options.put("callback_url","https://www.sina.com.cn/");
//plugins 非必填 备注信息
options.put("plugins","我是备注信息");
// nonce_str 必填 随机值 32位内 作用: 1.避免服务器页面缓存,2.防止安全密钥被猜测出来(md5 密钥越复杂,就越难解密出来)
options.put("nonce_str","740969606");
// 定义 sb 为了获取 MD5 加密前的字符串
StringBuilder sb = new StringBuilder();
// 将HashMap 进行 键 的 Ascll 从小到大排序 并 将每个 hashmap元素 以 & 拼接起来
options.entrySet().stream().sorted((e1,e2) -> e1.getKey().compareTo(e2.getKey())).forEach(a ->{
sb.append(a).append("&");
});
// 去除 最后一位的 &
sb.deleteCharAt(sb.length()-1);
// 拼接上密钥
sb.append(appsecret);
// 调用 Hutool 的 加密工具 进行 MD5 加密
String s = SecureUtil.md5(sb.toString());
// 输出hash结果 postman 要用
System.out.println("我们生成的Hash 是:"+s);
// 输出time结果 postman 要用
System.out.println("我们生成的time 是: "+options.get("time"));
System.out.println();
// 必填 hash 签名
options.put("hash", s);
System.out.println("我们传递的参数有:"+options.toString());
System.out.println("开始调 虎皮椒支付 接口...");
// 调用 Hutool 的HttpUtil 发送 post 请求
String post = HttpUtil.post(url, options);
System.out.println("结束调 虎皮椒支付 接口...\n");
System.out.println("虎皮椒支付 接口 响应的结果是:"+post+"\n");
// 说明:这里 因为虎皮椒支付 响应结果 不统一,正确是Json;不正确 就是一行String 。没办法 判断是否请求是否有效。所以 只能通过 是由能够解析成json 有无异常判断 是否调用成功
try{
Map map = (Map)JSON.parse(post);
map.keySet().stream().forEach(k -> {
if (k == "url") {
System.out.println("url二维码链接是: "+map.get(k));
}
});
}catch (Exception e){
e.printStackTrace();
System.out.println("调 虎皮椒支付 时 出现了问题");
}


}

/**
* 获取精确到秒的时间戳 原理 获取毫秒时间戳,因为 1秒 = 100毫秒 去除后三位 就是秒的时间戳
* @return
*/
public static int getSecondTimestamp(Date date){
if (null == date) {
return 0;
}
String timestamp = String.valueOf(date.getTime());
int length = timestamp.length();
if (length > 3) {
return Integer.valueOf(timestamp.substring(0,length-3));
} else {
return 0;
}
}

后来经过我的上十次测试,支付成功后回调(notify_url)没用,就选择第二种方法吧,页面每隔几秒查询次支付状况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Test
public void test(){
// appid
String appid = "";
// appsecret
String appsecret = "";
// 请求路径
String url = "https://api.xunhupay.com/payment/query.html";
// 设置 传递参数的集合,方便 传递数据。
Map<String,Object> options = new HashMap<>();
// 必填 设置 appid
options.put("appid",appid);
// 必填 订单号 具体内容自己控制 长度 32位 官网说 请确保在当前网站内是唯一订单号,具体含义 我测试了 在描述 此备注
options.put("out_trade_order","1234567890");
// 必填 当前时间戳 调用 刚写的方法 getSecondTimestamp
options.put("time", getSecondTimestamp(new Date()));
// nonce_str 必填 随机值 32位内 作用: 1.避免服务器页面缓存,2.防止安全密钥被猜测出来(md5 密钥越复杂,就越难解密出来)
options.put("nonce_str","123546789");
// 定义 sb 为了获取 MD5 加密前的字符串
StringBuilder sb = new StringBuilder();
// 将HashMap 进行 键 的 Ascll 从小到大排序 并 将每个 hashmap元素 以 & 拼接起来
options.entrySet().stream().sorted((e1,e2) -> e1.getKey().compareTo(e2.getKey())).forEach(a ->{
sb.append(a).append("&");});
// 去除 最后一位的 &
sb.deleteCharAt(sb.length()-1);
sb.append(appsecret);
// 调用 Hutool 的 加密工具 进行 MD5 加密
String s = SecureUtil.md5(sb.toString());
// 必填 hash 签名
options.put("hash", s);
System.out.println("我们传递的参数有:"+options);
// 调用 Hutool 的HttpUtil 发送 post 请求
String post = HttpUtil.post(url, options);
System.out.println("结束调 虎皮椒支付 接口...\n");
System.out.println("虎皮椒支付 接口 响应的结果是:"+post+"\n");
JSONObject jsonObject = JSON.parseObject(post);
// 获取data对象
JSONObject dataObject = jsonObject.getJSONObject("data");
// 获取status值
String status = dataObject.getString("status");
System.out.println(status);
}

当然了上面的全只是测试类,下面上真的实战代码,也就是我的Pass Assistant的分享:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
@Value("${hupijiao.appid}")
private String AppId;

@Value("${hupijiao.appsecret}")
private String AppSecret;

@Value("${hupijiao.webUrl}")
private String webUrl;

/**
* 虎皮椒支付宝支付
* @param productId the product id
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public AlipayPayCodeVo generatePayQrCode(final Long productId) {
Product product = productMapper.selectOne(new QueryWrapper<Product>().lambda().eq(Product::getProductId, productId));
if (product == null) {
throw new OrdersException(ExceptionMessages.PRODUCT_NULL_ERR, 500);
}
// 请求路径
String url = "https://api.xunhupay.com/payment/do.html";
// 设置 传递参数的集合,方便 传递数据。
Map<String,Object> options = new HashMap<>();
// 必填 设置版本号
options.put("version","1.1");
// 必填 设置 appid
options.put("appid",AppId);
// 必填 订单号 具体内容自己控制 长度 32位
// 官网说 请确保在当前网站内是唯一订单号,具体含义 我测试了 在描述 此备注
SecureRandom secureRandom = new SecureRandom();
StringBuilder stringBuilder = new StringBuilder(12);
for (int i = 0; i < 12; i++) {
stringBuilder.append(secureRandom.nextInt(12)); // 生成0-9之间的随机数
}
String randomNumber = stringBuilder.toString();
options.put("trade_order_id",randomNumber);
// 必填 价格 精确到RMB分
String formattedNumber = String.format("%.2f", product.getProductPrice());
options.put("total_fee",formattedNumber);
// 必填 标题
options.put("title",product.getProductName());
// 必填 当前时间戳 调用 刚写的方法 getSecondTimestamp
options.put("time", getSecondTimestamp(new Date()));
// 必填 通知回调地址 url 什么含义 我们后台需要知道 用户支付了。
options.put("notify_url","https://java.hepingan.top/pay/paycallback"); // 只有这个有用
// 非必填 使用 响应字段中 url 就直接跳到百度了,如果访问,url_qrcode ,不会直接跳转,只有当支付完成后,再次刷新 url_qrcode中的连接,才会跳转。
options.put("return_url",webUrl+"/#/user_view");
// 非必填 用户取消支付,跳转的页面 经过测试,没有触发机制,建议不传递
options.put("callback_url",webUrl+"/#/user_view");
//plugins 非必填 备注信息
options.put("plugins","您支付后的IT币(虚拟货币)将只用于Pass Assistant站内消费.");
// nonce_str 必填 随机值 32位内 作用: 1.避免服务器页面缓存,2.防止安全密钥被猜测出来(md5 密钥越复杂,就越难解密出来)
SecureRandom secureRandom2 = new SecureRandom();
StringBuilder stringBuilder2 = new StringBuilder(10);
for (int i = 0; i < 10; i++) {
stringBuilder2.append(secureRandom2.nextInt(10)); // 生成0-9之间的随机数
}
String randomNumber2 = stringBuilder2.toString();
options.put("nonce_str",randomNumber2);
// 定义 sb 为了获取 MD5 加密前的字符串
StringBuilder sb = new StringBuilder();
// 将HashMap 进行 键 的 Ascll 从小到大排序 并 将每个 hashmap元素 以 & 拼接起来
options.entrySet().stream().sorted((e1,e2) -> e1.getKey().compareTo(e2.getKey())).forEach(a ->{
sb.append(a).append("&");});
// 去除 最后一位的 &
sb.deleteCharAt(sb.length()-1);
// 拼接上密钥
sb.append(AppSecret);
// 调用 Hutool 的 加密工具 进行 MD5 加密
String s = SecureUtil.md5(sb.toString());
// 必填 hash 签名
options.put("hash", s);
// 调用 Hutool 的HttpUtil 发送 post 请求
String post = HttpUtil.post(url, options);
// 说明:这里 因为虎皮椒支付 响应结果 不统一,正确是Json;
// 不正确 就是一行String 。没办法 判断是否请求是否有效。
// 所以 只能通过 是由能够解析成json 有无异常判断 是否调用成功
try{
Map<String, Object> map = (Map<String, Object>) JSON.parse(post);
String payUrl = (String) map.get("url");
LocalDateTime createdTime = LocalDateTime.now();
Orders orders = new Orders()
.setOrdersId(randomNumber)
.setUserId(UserUtils.getCurrentLoginId())
.setCreatedTime(createdTime)
.setUpdateTime(createdTime)
.setProductId(productId)
.setProductName(product.getProductName())
.setProductPrice(product.getProductPrice())
.setState(0);
ordersMapper.insert(orders);
return new AlipayPayCodeVo()
.setOrdersId(orders.getOrdersId())
.setCreatedTime(orders.getCreatedTime())
.setProductName(orders.getProductName())
.setProductPrice(orders.getProductPrice())
.setPayUrl(payUrl);
}catch (OrdersException e){
e.printStackTrace();
return null;
}

}

/**
* 获取精确到秒的时间戳 原理 获取毫秒时间戳,因为 1秒 = 100毫秒 去除后三位 就是秒的时间戳
* @return
*/
public static int getSecondTimestamp(Date date){
if (null == date) {
return 0;
}
String timestamp = String.valueOf(date.getTime());
int length = timestamp.length();
if (length > 3) {
return Integer.valueOf(timestamp.substring(0,length-3));
} else {
return 0;
}
}



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/**
* 检查 付款状态
* @param orderNo 订单
* @return {@link String}
*/
@Override
public String paymentStatus(final String orderNo) {
// 请求路径
String url = "https://api.xunhupay.com/payment/query.html";
// 设置 传递参数的集合,方便 传递数据。
Map<String,Object> options = new HashMap<>();
// 必填 设置 appid
options.put("appid",AppId);
// 必填 订单号 具体内容自己控制 长度 32位 官网说 请确保在当前网站内是唯一订单号,具体含义 我测试了 在描述 此备注
options.put("out_trade_order",orderNo);
// 必填 当前时间戳 调用 刚写的方法 getSecondTimestamp
options.put("time", getSecondTimestamp(new Date()));
// nonce_str 必填 随机值 32位内 作用: 1.避免服务器页面缓存,2.防止安全密钥被猜测出来(md5 密钥越复杂,就越难解密出来)
SecureRandom secureRandom2 = new SecureRandom();
StringBuilder stringBuilder2 = new StringBuilder(10);
for (int i = 0; i < 10; i++) {
stringBuilder2.append(secureRandom2.nextInt(10)); // 生成0-9之间的随机数
}
String randomNumber2 = stringBuilder2.toString();
options.put("nonce_str",randomNumber2);
// 定义 sb 为了获取 MD5 加密前的字符串
StringBuilder sb = new StringBuilder();
// 将HashMap 进行 键 的 Ascll 从小到大排序 并 将每个 hashmap元素 以 & 拼接起来
options.entrySet().stream().sorted((e1,e2) -> e1.getKey().compareTo(e2.getKey())).forEach(a ->{
sb.append(a).append("&");});
// 去除 最后一位的 &
sb.deleteCharAt(sb.length()-1);
sb.append(AppSecret);
// 调用 Hutool 的 加密工具 进行 MD5 加密
String s = SecureUtil.md5(sb.toString());
// 必填 hash 签名
options.put("hash", s);
// 调用 Hutool 的HttpUtil 发送 post 请求
String post = HttpUtil.post(url, options);

JSONObject jsonObject = JSON.parseObject(post);
// 获取data对象
JSONObject dataObject = jsonObject.getJSONObject("data");
// 获取status值
String status = dataObject.getString("status");
System.out.println(status);
if (Objects.equals(status, "OD")){
ordersMapper.update(null,new UpdateWrapper<Orders>()
.lambda()
.eq(Orders::getOrdersId,orderNo)
.set(Orders::getState,1)
.set(Orders::getPayTime,LocalDateTime.now()));
Long frequency = userMapper.getFrequencyById(UserUtils.getCurrentLoginId());
Orders orders = ordersMapper.selectOne(new QueryWrapper<Orders>()
.lambda()
.eq(Orders::getOrdersId,orderNo)
.select(Orders::getProductId));
Product product = productMapper.selectOne(new QueryWrapper<Product>()
.lambda()
.eq(Product::getProductId, orders.getProductId())
.select(Product::getFrequency));
//更新用户的次数
userMapper.update(null,new UpdateWrapper<User>()
.lambda()
.eq(User::getUserId,UserUtils.getCurrentLoginId())
.set(User::getFrequency,frequency+product.getFrequency()));
return "success";
}else if (Objects.equals(status, "WP")){
return "wait";
}else {
return "cancel";
}
}

前端:

1
2
3
4
5
6
7
8
9
10
11
12
<div v-if="payying"
style="text-align: center; padding-top: 100px"
>
<h1>请跳转到新的页面进行支付</h1>
<h2 style="color: red">支付成功前请勿关闭或刷新该页面</h2>
</div>
<div v-if="showSucceed"
style="text-align: center; padding-top: 100px">
<h1>恭喜您已成功支付</h1>
<el-button type="primary" @click="router().push('/user_view')">查看</el-button>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
async function alipayPay() {
try {
ElLoading.service({
fullscreen: true,
text: "正在构建订单...",
spinner: "el-icon-loading",
background: "rgba(0, 0, 0, 0.7)",
});
//构建支付宝订单
outcome.value = await alipayPayQrCode(productId.value);
payVisible.value = false;
payying.value = true;
mainPageVisible.value = false;
window.open(outcome.value.payUrl, '_blank');
//3秒检查一下订单是否支付成功
let timerId = setInterval(async function () {
let res = await alipayIsSucceed(outcome.value.ordersId);
if (res === "success") {
ElNotification({
title: "成功",
message: "赞赏成功,可在我的赞赏中查看该赞赏记录",
type: "success",
});
showSucceed.value = true;
await getUser();
clearInterval(timerId);
} else if (res === "cancel") {
showCover.value = true;
ElNotification({
title: "订单已关闭",
message: "长时间未支付,订单已关闭",
type: "error",
});
clearInterval(timerId);
}
//间隔时间查询
}, 5000);
} catch (e) {
ElNotification({
title: "错误",
message: e,
type: "error",
});
mainPageVisible.value = true;
} finally {
ElLoading.service().close();
}
}

看得懂就看啦,把那个测试类看懂就会了,我这个仅供参考