50 Commits

Author SHA1 Message Date
技术老胡
aff1c51227 更新插件管理 2025-03-01 12:54:53 +08:00
技术老胡
6e42b0359e 更新插件管理 2025-02-28 15:43:18 +08:00
技术老胡
d5c43e28af 更新插件中心功能 2025-02-28 14:23:39 +08:00
技术老胡
3b02869b5d 更新文档 2025-02-25 11:40:56 +08:00
技术老胡
92a5ce6edb 更新插件管理 2025-02-25 11:37:33 +08:00
技术老胡
1d55442388 优化插件管理逻辑 2025-02-24 13:07:07 +08:00
技术老胡
c469680ae4 修改支付宝部分情况不回调问题 2025-02-22 20:18:58 +08:00
技术老胡
f247f54daf 更新订单生成逻辑提醒 2025-02-22 20:12:55 +08:00
技术老胡
d92daaa1ae 更新代码结构 2025-02-21 18:53:21 +08:00
技术老胡
6f6355e57f 更新文档 2025-02-19 12:26:23 +08:00
技术老胡
43932ec0d9 更新文档 2025-02-19 12:18:20 +08:00
技术老胡
aaf0fd96e7 更新文档 2025-02-19 11:19:19 +08:00
技术老胡
2f0408a463 更新 2025-02-19 11:01:52 +08:00
技术老胡
d53b31cb1e 更新 2025-02-19 11:01:25 +08:00
技术老胡
53a0a75d93 更新 2025-02-19 10:59:27 +08:00
技术老胡
795e84bd96 更新文件 2025-02-19 10:54:55 +08:00
技术老胡
d3e8749e29 更新文档 2025-02-19 10:47:43 +08:00
技术老胡
049ab97702 更新文档 2025-02-19 10:35:59 +08:00
技术老胡
5576a606e4 更新文档 2025-02-18 14:01:10 +08:00
技术老胡
dc1ee1a226 新增支付宝账单查询插件 2025-02-18 13:22:26 +08:00
技术老胡
67153084c8 更新 2025-02-17 11:32:33 +08:00
技术老胡
686227eeba 更新 2025-02-17 11:22:12 +08:00
技术老胡
9791b0c017 更新 2025-02-17 11:16:45 +08:00
技术老胡
023c05e5e3 更新文档 2025-02-17 11:15:15 +08:00
技术老胡
fc35815ae4 更新文档 2025-02-17 11:10:42 +08:00
技术老胡
df6b7f7c22 更新文档 2025-02-17 11:02:43 +08:00
技术老胡
06a062546c 更新文档 2025-02-13 21:10:56 +08:00
技术老胡
f03d3de109 添加收款码设置说明 2025-02-08 11:37:14 +08:00
技术老胡
b546977011 更新PC端监控逻辑 2025-02-08 10:53:26 +08:00
技术老胡
9080543df4 更新文档 2025-02-06 18:34:12 +08:00
技术老胡
a4b8381395 更新文档 2025-01-24 15:51:58 +08:00
技术老胡
020398b34f 更新 2025-01-24 15:44:53 +08:00
技术老胡
a7ca3d2db5 更新文档 2025-01-24 15:42:30 +08:00
技术老胡
5d42d2ef70 更新 2025-01-22 17:08:30 +08:00
技术老胡
9fbedc8cfc 更新文件 2025-01-20 10:30:53 +08:00
技术老胡
b56156d33a 更新手动补单问题 2025-01-20 10:00:07 +08:00
技术老胡
f9c804c1e5 更新订单回调通知逻辑 2025-01-18 10:57:16 +08:00
技术老胡
2515515182 更新插件 2025-01-04 10:21:32 +08:00
技术老胡
87563d01d7 更新收银台付款环境检测 2025-01-02 11:13:40 +08:00
技术老胡
16f4438a16 更新 2024-12-30 10:57:33 +08:00
技术老胡
29c894faae 新增安装环境检测 2024-12-30 10:56:14 +08:00
技术老胡
a5d09d0a26 新订单缓存更新,更利于维护 2024-12-30 10:03:46 +08:00
技术老胡
504aa6c284 添加插件远程访问缓存 2024-12-30 09:28:59 +08:00
技术老胡
e65c0e9173 修改金额核对值对象,防止漏单 2024-12-28 16:05:36 +08:00
技术老胡
2c1ece3eb2 更新插件 2024-12-28 11:46:51 +08:00
技术老胡
ebcac8a9aa 更新文件 2024-12-23 16:47:59 +08:00
技术老胡
4eef7f0797 更新二维码通道添加指引 2024-12-23 15:24:57 +08:00
技术老胡
dfb8edff22 修复通道测试服务器报500错误 2024-12-21 20:16:06 +08:00
技术老胡
05c1b08e95 修复图片上传文件漏洞 2024-12-20 11:16:57 +08:00
技术老胡
de848ab32b 更新一些细节 2024-12-18 17:56:40 +08:00
33 changed files with 1118 additions and 298 deletions

6
.env
View File

@@ -3,10 +3,10 @@ APP_DEBUG = true
DB_TYPE = mysql
DB_HOST = 127.0.0.1
DB_NAME = mpay
DB_USER = admin
DB_PASS = Aa123456
DB_USER = mpay
DB_PASS = 123456
DB_PORT = 3306
DB_CHARSET = utf8
DB_CHARSET = utf8mb4
DB_PREFIX = mpay_
DEFAULT_LANG = zh-cn

17
.gitignore vendored
View File

@@ -1,6 +1,15 @@
/.idea
/.vscode
/.history
/.gitee
.vscode
*.env
extend/*
!extend/ImgCaptcha.php
!extend/payclient.zip
!extend/Plugin.php
vendor/*
!vendor/vendor.zip
runtime/*
app/*
!app/controller/TestController.php

208
README.md
View File

@@ -1,138 +1,138 @@
# 码支付
<p align="center">
<div align="center">
<a href="https://gitee.com/technical-laohu/mpay">
<img src="assets/20241129_120237_logo.jpg" alt="mpay" width=400 />
</a>
</div>
<div align="center">
<a href="https://gitee.com/technical-laohu/mpay" target="_blank">项目主页</a>
<a href="https://gitee.com/technical-laohu/mpay/releases" target="_blank">源码下载</a>
<a href="https://f0bmwzqjtq2.feishu.cn/docx/HBVrdrsACo36bzxUCSPcjOBNnyb" target="_blank">使用文档</a>
<a href="https://f0bmwzqjtq2.feishu.cn/docx/FtphdDA10oBfPyxNEEZc5mgJnqf" target="_blank">常见问题</a>
<a href="https://f0bmwzqjtq2.feishu.cn/docx/OjlwdPunLoGjL0xodMUcS0xFngX" target="_blank">学习交流</a>
<a href="https://f0bmwzqjtq2.feishu.cn/docx/PjwOdvBeZoQEHUxF2ZScTjHOnKb" target="_blank">赞赏作者</a>
</div>
<br />
<div align="center">
😎免签约、🧩多通道、🛜不掉线 - 专注于个人在线收款💴
</div>
</p>
![logo](assets/20241129_120237_logo.jpg)
## ✨ 项目介绍
**码支付[mpay]是一款便捷收款工具,专注于个人免签收款,通过普通收款码即可实现收款通知自动回调,支持绝大多数商城系统**
# 项目地址
| gitee | github |
| <p align="center">gitee</p> | <p align="center">github</p> |
| :----------: | :------------: |
|[![](assets/20241122_113208_gitee.png)](https://gitee.com/technical-laohu/mpay)|[![](assets/20241122_113331_github.png)](https://github.com/techhaha/mpay)|
| <a href="https://gitee.com/technical-laohu/mpay"><img src="assets/20241122_113208_gitee.png" alt="mpay" width=128 /></a>|<a href="https://github.com/techhaha/mpay"><img src="assets/20241122_113331_github.png" alt="mpay" width=128 /></a>|
点以上图标进入项目页面,可查看最新发行版
# 项目说明
## ⚙️ 工具特性
## 原因
- 开源程序,个人免费使用,不断更新
- 支持第四方收款服务商聚合码收款,免挂机、不掉线
- 支持微信、支付宝个人账户收款,免签约
- 采用易支付接口标准开发,兼容市面上大部分商城系统
- 在H5环境中能正常长按识别扫码支付
- 支持多平台、多账号、多通道,灵活配置,收款轮询
## ✨ 演示站点
访问 [在线演示](http://demo.stspwsc.com/) 快速体验。
## 📊 项目说明
### 🎈 原因
之前在工作之余,尝试过很多副业项目,基本都是跟互联网相关的。例如使用**Wordpress和zibll主题**建资源站点,用**微擎**和**微课堂V2**卖课程,使用**异次元发卡**和**独角数卡**程序卖代理的**微信营销软件**,其中遇到的让我最头痛的问题就是收款问题。
正规官方渠道,如微信支付宝申请相关支付接口,必须要签约,且有营业执照等相关资质,这对于只想搞想副业的我实在是没必要,也麻烦,后期的事情也多。于是在网上找到了一些解决方案来解决我的在线收款问题。
---
## 方案
### 🪄 方案
市场也有很多针对此类需求的平台,也有不少合适的解决方案
### 虎皮椒/迅虎/蓝兔支付
#### 虎皮椒/迅虎/蓝兔支付
这些平台都是微信支付宝等官方平台的支付服务商能签约个人商户实现在线收款一些API支付接口可以直接调用跟官方自己申请的几乎差不多。
优点是跟官方申请的支付接口差不多N+1到账
缺点就是审核比较严动不动投诉封商户号一些羊毛党就爱整你你是一点办法没有另外开户基本都要交开户费50-200不等且还需要额外收取手续费。
### 彩虹易支付
#### 彩虹易支付
彩虹易支付是一套收款程序,有专门的公司或平台用这套程序搭建一个收款平台,使用自己的微信或支付宝等官方账户来进行收款,你自己注商户号,运营方提供代收款服务,然后给你打款结算。
优点是审核不严,处理比较灵活,适合个人,技术支持比较好,注册可使用(有些需要注册费)
缺点就是平台容易卷款跑路,也没有什么有效监管,钱收不回来就亏大了(这也是最大的问题)
### 源支付/V免签
#### 源支付/V免签
源支付也是一套收款程序,有个人版和商户版,市面上能搜到的大部分都是商户版,可以入驻,使用自己的个人微信支付宝二维码收款。
V免签是一款开源免费适用于个人收款使用的收款程序原理同源支付类似。
程序的设计思路主要是通过在手机或电脑上安装消息监听软件,用来监听获取微信和支付宝的收款到账通知来实现的支付成功回调的。**方法很实用,本程序也添加了该功能插件,免费**。
![](assets/20241210_112331_e8d2c4043a3c57ad887aef92df1c253.png)
只是这种思路,有一些小问题:
## ✨ 开发思路
* 平台容易因为资质问题导致关站;
* 收取的手续费价格偏高;
* 个人码在微信H5环境无法长按识别付款只能通过PC端相机扫码付款。
* 挂机监听容易掉线,导致收款通知无法回调
### 🎯 服务商聚合码
### 🚀️ 码支付(mpay)
**本程序暂只提供个人版,开源免费使用。**
码支付是在源支付的设计思路基础上进行的改进,利用第四方**聚合收款码**来进行收款,保证收款稳定和便捷不掉线。
聚合收款码个人可以申请不需求相关资质不用申请API接口收银服务平台众多且实力雄厚如拉卡拉、收钱吧等不怕跑路。
特点如下:
* 免监听,不需要手机或电脑挂机监听消息,即可实现支付回调,只需要设置一个定时任务就行
* 支持微信、支付宝、云闪付的H5环境能正常长按识别扫码支付域名防红
* 个人搭建的收款系统,收款稳定,安全可控,不需要额外手续费
* 支持多平台(聚合码服务商),多账号(聚合码商户),多渠道(门店码/店员码/桌号码等),降低异地线上收款风控风险
---
## 思路
<img src="assets/20241210_112301_6f2fef2a7aaee96790eb86f90e3b107.png" width=640 />
码支付说到底就是通过二维码来进行收款,日常使用的除了微信支付宝生成的二维码外,还有一类二维码是由收款服务商提供的,它能通过一张收款二维码,同时支持**微信**、**支付宝**、**云闪付**等多渠道付款,一般称为**聚合收款码**。
![聚合收款码](assets/20241128_164241_image.png)
<img src="assets/20241128_164241_image.png" width=640 />
这类收款码扫码之后需要用户自己输入指定金额来进行付款,然后查看收款通知,确认是否到账,最后确认订单支付成功。
就像你去店子里买一瓶水,你扫二维码进去付款界面,就生成了一个订单,你付款成功之后,商店老板会去查看一下商户后台流水,确认订单是否支付成功,这是一个人工审核的过程。
![商户后台订单流水](assets/20241128_164817_image.png)
<img src="assets/20241128_164817_image.png" width=640 />
那么码支付的作用,就是让人工审核变成自动审核的,当用户通过网站购买商品的时候,码支付会生成一个订单并展示收银台界面,用户再扫码进入聚合码付款页面。
![码支付收银台](assets/20241128_164952_image.png)
<img src="assets/20241128_164952_image.png" width=65% />
![用户付款](assets/20241128_171356_image.png)
<img src="assets/20241128_171356_image.png" width=32% />
同一时间,**码支付后台会自动通过账号密码登陆聚合码服务平台的管理后台**并通过API接口循环查询最近的收款明细通过比对金额和时间确认是否付款到账最后确认成功收款。
![收款成功](assets/20241128_165320_image.png)
<img src="assets/20241128_165320_image.png" width=640 />
当用户付款成功,并且后台检测到收款成功消息后,收钱台就会提示收款成功,并最终确认收款。
![](assets/20241210_112301_6f2fef2a7aaee96790eb86f90e3b107.png)
> 只有存在新订单时,且该订单与当前收款账号一致时,码支付后台才会主动登陆该账号,查询收款流水,减少频繁查询导致的可能风险
> 另外,在账号设置里也有两个模式可选,`单次监听`和`连续监听`,根据业务场景可以自行选择,具体使用,下面有介绍
# 开源声明
### 🪁 微信、支付宝收款码
## 演示站点
<img src="assets/20241210_112331_e8d2c4043a3c57ad887aef92df1c253.png" width=640 />
[码支付](http://demo.stspwsc.com/) http://demo.stspwsc.com/
普通的个人收款码,大多使用微信/支付宝来进行收款,依赖于微信/支付宝平台的推送服务和系统通知机制,就可以获取到收款金额的通知信息,从而可以实现支付回调,自动确认收款。
目前主流的方式也是这种,来实现自动收款,简单方便,不过有一些小问题:
- 微信/支付宝的收款通知推送,需要软件挂机进行监听,否则无法确认收款
- 监听软件因手机或PC系统的不同可能会出现各种监听不稳定的问题
- 微信个人码因升级在微信H5环境里无法长按识别付款
## 内置插件
码支付收款管理系统开源免费使用,支付插件有**免费**和**付费**版本,按需使用购买
1. **微信**插件`wxpay`默认安装,支持`赞赏码``个人码|经营码|商家码`2个通道需挂机监听
2. **支付宝**插件`alipay`默认安装,支持`收钱码``经营码`2个通道需挂机监听
3. **收钱吧**聚合码插件`sqbpay`默认安装,无需挂机,设置定时任务即可
## 技术架构
## 📦 技术架构
使用`Thinkphp8`框架PHP版本 > 8.0推荐8.2前端UI使用`Layui 2.9`+`PearAdmin`后台
## 🧰 内置插件
码支付收款工具开源免费使用,插件中心的插件列表有**免费版**和**付费版**版,按需使用购买
1. **微信**插件`wxpay`默认安装,支持`赞赏码``个人码``经营码``商家码`,需挂机监听
2. **支付宝**插件`alipay`默认安装,支持`收钱码``经营码`,需挂机监听
3. **收钱吧**聚合码插件`sqbpay`默认安装,无需挂机,设置定时任务即可监听回调
---
# 安装和使用
## 全新安装
## 📝 安装说明
以下演示基于**云服务器**环境+**宝塔面板**安装,云服务器购买可以去阿里云、腾讯云等平台,宝塔面板安装教程参考[宝塔面板安装教程](https://www.bt.cn/new/download.html)
### 🚀️ 源码下载
**点击下载 [码支付 v1版本](https://gitee.com/technical-laohu/mpay/releases)**
**更多版本请关注发行版更新记录**
### 安装配置
以**宝塔面板**示例,其他服务器管理面板可以参考
@@ -182,7 +182,8 @@ V免签是一款开源免费适用于个人收款使用的收款程序原理
---
## ❤️ 聚合码使用
## ❤️ 聚合码收款
如果本身就有聚合码收钱码最好,没有就需要提前去各收银服务商申请,申请也不复杂
@@ -285,7 +286,7 @@ V免签是一款开源免费适用于个人收款使用的收款程序原理
---
## ❤️ 微信/支付宝使用
## ❤️ 微信/支付宝收款
微信/支付宝生成的收款码,需要挂机监听收款消息,基本情况上面有介绍,因为使用广泛,所以也单独添加进来,可以实现正常收款回调
@@ -319,9 +320,9 @@ V免签是一款开源免费适用于个人收款使用的收款程序原理
### 挂机监听收款通知
`微信支付``支付宝`需要手机挂机监听收款通知,并通知服务器收款信息,也是最常见的解决方案
收款通知信息需要挂机监听,有`手机监听``PC监听`两种手机可以同时监听微信和支付宝PC只能监听微信具体支持的通知和功能在设置账号页面有详细介绍。
#### 软件下软(安卓)
#### 🚀️ 手机软件下软(安卓)
码支付使用的是开源工具**短信转发器**`SmsForwarder`来监听收款通知,`SmsForwarder`功能非常强大,喜欢*搞机* 的朋友可以多钻研,这是使用文档[短信转发器](https://gitee.com/pp/SmsForwarder/wikis/pages)
@@ -333,7 +334,7 @@ V免签是一款开源免费适用于个人收款使用的收款程序原理
---
#### 🚀️ 功能配置
##### 功能配置
找一台不常用的安卓系统手机,下载安装好`SmsForwarder`之后,点开进行配置,同时打开码支付后台**用户中心**页面,查看相关配置参数
@@ -403,16 +404,68 @@ V免签是一款开源免费适用于个人收款使用的收款程序原理
![](assets/20241204_173046_image.png)
#### 收款通知测试
#### 🚀️ PC软件下载
码支付微信PC监听是老胡使用python写的小工具可以监听电脑桌面打开的窗口和内容信息非侵入微信应用内部版本升级不影响监听。需要将被监听的微信聊天界面单独拖出来成独立窗口。
[PC监听软件下载wxmonitor.zip](assets/20250217_103606_wxmonitor.zip)
##### 配置信息
1. 打开用户中心,添加接口密钥等信息
![](assets/20250217_104137_image.png)
2. 打开账号管理列表,填写通道信息,格式`pid#aid`
![](assets/20250217_104349_image.png)
3. 循环间隔是软件检索窗口通知信息的循环时间一般设置1-3秒均可看电脑性能
4. 监控窗口根据实际情况勾选,哪个窗口需要检索收款通知,就开启哪个,不使用的就不需要开启
![](assets/20250217_105144_image.png)
5. PC监听同时支持个人码、经营码、赞赏码、商户码**手机监听因无法区分个人码和经营码,添加时只能二选一,且只能在`个人码`通道添加**。
![](assets/20250217_105939_image.png)
7. PC监听和手机监听只需要开启一个即可不要同时开启会出现重复通知导致订单异常。
---
# 补充说明
## 🔎 补充说明
* 该软件仅适用于个人线上免签收款,避免人工审核确认收款的繁琐步骤。请勿用于诈骗、黑灰产业,如有此类行为,后果自负。
* 插件中心目前还在开发中不支持在线安装插件如果有购买插件或定制其他收银平台插件的需求可以联系作者Wechat:**K103516**
- **码支付**是一个**个人免费使用**的收款工具,**码支付**的**源码**是开源的,你可以根据自己的实际情况进行修改,也可以自行开发,有任何问题,可以社区交流
- 该软件仅适用于个人线上免签收款,避免人工审核确认收款的繁琐步骤。实际使用中请勿用于诈骗、黑灰产业,如有此类行为,一经发现直接举报
- 插件中心目前还在开发中不支持在线安装插件如果有购买插件或定制其他收银平台插件的需求可以联系作者Wechat:**K103516**
# 页面展示
## 📬 学习交流
社群答疑、插件定制、技术交流,添加微信拉群,请备注:**码支付**
微信:**K103516**
<img src="assets/wxqrcode.png" width=480 />
## 💰 赞赏作者
<img src="assets/20250217_111408_1739762042428.jpg" width=360 />
|姓名 | 金额 |
|---|---|
| 知汇学社 | 100 |
| exrock | 100 |
| A筱磊 | 6.66 |
| 云峰 | 8.88 |
| 零零 | 200 |
| 子染 | 88 |
## 🔗 友情链接
1. [腾飞博客专业WP子比主题美化定制](https://calfbxtoi72.feishu.cn/docx/DA3ddthzOolQ16xDgeOcHA36nrg)
## 📷 项目截图
![首页](assets/20241203_153935_image.png)
@@ -434,5 +487,4 @@ V免签是一款开源免费适用于个人收款使用的收款程序原理
![收款码](assets/20241203_154918_image.png)
![订单详情](assets/20241203_155123_image.png)
![订单详情](assets/20241203_155123_image.png)

496
README_old.md Normal file
View File

@@ -0,0 +1,496 @@
# 码支付
![logo](assets/20241129_120237_logo.jpg)
**码支付[mpay]是一款便捷收款工具,专注于个人免签收款,通过普通收款码即可实现收款通知自动回调,支持绝大多数商城系统**
# 项目地址
| gitee | github |
| :----------: | :------------: |
|[![](assets/20241122_113208_gitee.png)](https://gitee.com/technical-laohu/mpay)|[![](assets/20241122_113331_github.png)](https://github.com/techhaha/mpay)|
点以上图标进入项目页面,可查看最新发行版
# 项目说明
## 原因
之前在工作之余,尝试过很多副业项目,基本都是跟互联网相关的。例如使用**Wordpress和zibll主题**建资源站点,用**微擎**和**微课堂V2**卖课程,使用**异次元发卡**和**独角数卡**程序卖代理的**微信营销软件**,其中遇到的让我最头痛的问题就是收款问题。
正规官方渠道,如微信支付宝申请相关支付接口,必须要签约,且有营业执照等相关资质,这对于只想搞想副业的我实在是没必要,也麻烦,后期的事情也多。于是在网上找到了一些解决方案来解决我的在线收款问题。
---
## 方案
市场也有很多针对此类需求的平台,也有不少合适的解决方案
### 虎皮椒/迅虎/蓝兔支付
这些平台都是微信支付宝等官方平台的支付服务商能签约个人商户实现在线收款一些API支付接口可以直接调用跟官方自己申请的几乎差不多。
优点是跟官方申请的支付接口差不多N+1到账
缺点就是审核比较严动不动投诉封商户号一些羊毛党就爱整你你是一点办法没有另外开户基本都要交开户费50-200不等且还需要额外收取手续费。
### 彩虹易支付
彩虹易支付是一套收款程序,有专门的公司或平台用这套程序搭建一个收款平台,使用自己的微信或支付宝等官方账户来进行收款,你自己注商户号,运营方提供代收款服务,然后给你打款结算。
优点是审核不严,处理比较灵活,适合个人,技术支持比较好,注册可使用(有些需要注册费)
缺点就是平台容易卷款跑路,也没有什么有效监管,钱收不回来就亏大了(这也是最大的问题)
### 源支付/V免签
源支付也是一套收款程序,有个人版和商户版,市面上能搜到的大部分都是商户版,可以入驻,使用自己的个人微信支付宝二维码收款。
V免签是一款开源免费适用于个人收款使用的收款程序原理同源支付类似。
程序的设计思路主要是通过在手机或电脑上安装消息监听软件,用来监听获取微信和支付宝的收款到账通知来实现的支付成功回调的。**方法很实用,本程序也添加了该功能插件,免费**。
![](assets/20241210_112331_e8d2c4043a3c57ad887aef92df1c253.png)
只是这种思路,有一些小问题:
* 平台容易因为资质问题导致关站;
* 收取的手续费价格偏高;
* 个人码在微信H5环境无法长按识别付款只能通过PC端相机扫码付款。
* 挂机监听容易掉线,导致收款通知无法回调
### 🚀️ 码支付(mpay)
**本程序暂只提供个人版,开源免费使用。**
码支付是在源支付的设计思路基础上进行的改进,利用第四方**聚合收款码**来进行收款,保证收款稳定和便捷不掉线。
聚合收款码个人可以申请不需求相关资质不用申请API接口收银服务平台众多且实力雄厚如拉卡拉、收钱吧等不怕跑路。
特点如下:
* 免监听,不需要手机或电脑挂机监听消息,即可实现支付回调,只需要设置一个定时任务就行
* 支持微信、支付宝、云闪付的H5环境能正常长按识别扫码支付域名防红
* 个人搭建的收款系统,收款稳定,安全可控,不需要额外手续费
* 支持多平台(聚合码服务商),多账号(聚合码商户),多渠道(门店码/店员码/桌号码等),降低异地线上收款风控风险
---
## 思路
码支付说到底就是通过二维码来进行收款,日常使用的除了微信支付宝生成的二维码外,还有一类二维码是由收款服务商提供的,它能通过一张收款二维码,同时支持**微信**、**支付宝**、**云闪付**等多渠道付款,一般称为**聚合收款码**。
![聚合收款码](assets/20241128_164241_image.png)
这类收款码扫码之后需要用户自己输入指定金额来进行付款,然后查看收款通知,确认是否到账,最后确认订单支付成功。
就像你去店子里买一瓶水,你扫二维码进去付款界面,就生成了一个订单,你付款成功之后,商店老板会去查看一下商户后台流水,确认订单是否支付成功,这是一个人工审核的过程。
![商户后台订单流水](assets/20241128_164817_image.png)
那么码支付的作用,就是让人工审核变成自动审核的,当用户通过网站购买商品的时候,码支付会生成一个订单并展示收银台界面,用户再扫码进入聚合码付款页面。
![码支付收银台](assets/20241128_164952_image.png)
![用户付款](assets/20241128_171356_image.png)
同一时间,**码支付后台会自动通过账号密码登陆聚合码服务平台的管理后台**并通过API接口循环查询最近的收款明细通过比对金额和时间确认是否付款到账最后确认成功收款。
![收款成功](assets/20241128_165320_image.png)
当用户付款成功,并且后台检测到收款成功消息后,收钱台就会提示收款成功,并最终确认收款。
![](assets/20241210_112301_6f2fef2a7aaee96790eb86f90e3b107.png)
> 只有存在新订单时,且该订单与当前收款账号一致时,码支付后台才会主动登陆该账号,查询收款流水,减少频繁查询导致的可能风险
> 另外,在账号设置里也有两个模式可选,`单次监听`和`连续监听`,根据业务场景可以自行选择,具体使用,下面有介绍
# 开源声明
## 演示站点
[码支付](http://demo.stspwsc.com/) http://demo.stspwsc.com/
## 内置插件
码支付收款管理系统开源免费使用,支付插件有**免费**和**付费**版本,按需使用购买
1. **微信**插件`wxpay`默认安装,支持`赞赏码``个人码|经营码|商家码`2个通道需挂机监听
2. **支付宝**插件`alipay`默认安装,支持`收钱码``经营码`2个通道需挂机监听
3. **收钱吧**聚合码插件`sqbpay`默认安装,无需挂机,设置定时任务即可
## 技术架构
使用`Thinkphp8`框架PHP版本 > 8.0推荐8.2前端UI使用`Layui 2.9`+`PearAdmin`后台
---
# 安装和使用
## 全新安装
以下演示基于**云服务器**环境+**宝塔面板**安装,云服务器购买可以去阿里云、腾讯云等平台,宝塔面板安装教程参考[宝塔面板安装教程](https://www.bt.cn/new/download.html)
### 🚀️ 源码下载
**点击下载 [码支付 v1版本](https://gitee.com/technical-laohu/mpay/releases)**
**更多版本请关注发行版更新记录**
### 安装配置
以**宝塔面板**示例,其他服务器管理面板可以参考
通过宝塔面板登陆管理后台新建PHP站点和数据库并确认创建
![](assets/20241203_161723_image.png)
在网站列表页面,点击创建的网站的根目录,进入文件管理
![](assets/20241203_162102_image.png)
文件夹里面有一些默认文件,不用管他
![](assets/20241203_162231_image.png)
点击上传文件,将源码压缩包上传到该文件夹,并解压到前文件夹
![](assets/20241203_162646_image.png)
**将`mpay`文件夹里面的所有文件,复制到当前根目录下**,返回网站列表管理页面
> 注意,压缩包文件打包的是一个名为`mpay`的文件夹,需要将代码文件夹里面的所有文件复制出来,放到创建的网站根目录下,
### 运行目录&仿静态 配置
点击网站名,进入网站配置设置页面
![](assets/20241203_163259_image.png)
选择**网站目录**,运行目录选择`public`,保存
![](assets/20241203_164321_image.png)
选择**伪静态**,模版选择`thinkphp`,即可自动填写,保存
![](assets/20241203_163507_image.png)
### 安装步骤
在浏览器输入`http://你的域名/install`,进入程序安装界面,按照提示进行填写提交
![](assets/20241203_165327_image.png)
数据库配置相关信息,在服务器管理面板里查找
![](assets/20241203_165507_image.png)
---
## ❤️ 聚合码使用
如果本身就有聚合码收钱码最好,没有就需要提前去各收银服务商申请,申请也不复杂
### 申请收款码
以下列出一些常见收款服务平台,可以按需申请,个人直接申请小微商户即可
| 平台 | 官网 |
|--------|--------------------------------------|
| 收钱吧 | https://www.shouqianba.com/ |
| 小Y经营 | https://xym.ysepay.com/ |
| 码钱 | https://m.hkrt.cn/ |
| 拉卡拉 | https://customer.lakala.com/ |
| 盛付通 | https://b.shengpay.com/ |
> 申请可以去官方平台注册账号等客服电话,或者在社群里询问(有很多人有代办资质),实在找不到的,可以去淘宝上的官方店买个二维码卡牌贴纸,然后询问客服如何开通账号就行,会有专员联系你开通。
### 安装插件
程序默认安装有**微信支付**`wxpay`、**支付宝**`alipay`、**收钱吧**`sqbpay`三个插件,基本能满足大家的日常收款需求。
如果需要其他收银平台插件,可以在插件中心自行安装,插件中心没有的,也可以联系作者定制开发。
### 添加账号
除**微信支付**`wxpay`、**支付宝**`alipay`之外,所有的收款平台均为聚合码收款平台
以**收钱吧**`sqbpay`为例,添加账号时,需要填写**收钱吧**商户管理中心的**登陆账号**和**登陆密码**,需要使用时,插件会自动在后台登陆账号并查询相关订单流水信息。
![](assets/20241205_102856_image.png)
![](assets/20241205_103718_image.png)
**监听模式**说明,分为`单次监听``连续监听`
![](assets/20241205_104101_image.png)
`单次监听`是在收银台页面,用户扫码支付成功后,需要手动点击**确认支付**,后台才会登陆查询该账号的收款流水情况,**点击一次,查询一次,不点击,则不会查询**,降低密集查询可能导致的账户风控
![](assets/20241205_104306_image.png)
`连续监听`是在创建订单之后,**在订单有效期内且订单未完成状态时**,插件会一直连续不断的查询账户流水详情,直到过期或者成功收款,用户不需要主动点击确认
![](assets/20241205_105844_image.png)
> 使用哪种监听模式,视应用场景自行选择,一般选择**连续监听**即可,如果日常订单比较密集,可以选择**单次监听**,或多添加几个账号,减少密集查询风控
### 添加收款码
点击可以查看当前账号的所有收款码
![](assets/20241205_110432_image.png)
点击**添加收款码**后,可以正常填写二维码信息
**终端编号**需要填写当前收款码在收银服务商系统内的编码,有的可以直接在收款二维码解析的**链接里找到**,有的需要**登陆商户管理中心**,去订单详情里查询才能知道
🚀️ 具体各个平台的终端编号如何获取,可以去**程序后端控制台主页**的`项目文档`查看🚀️
![](assets/20241205_110508_image.png)
![](assets/20241205_111720_image.png)
收款样式有两个选项`付款链接``图片地址`,根据实际情况选择
**付款链接:** 将二维码的内容解析成字符串保存,使用时再生成二维码
**图片地址:** 上传二维码图片到服务器或引用http远程图片地址
> 一般的**聚合收款码都解析成文字保存**即可,前端展示时会重新生成二维码,无法解析的就上传图片
### 🚀️ 设置监听
#### 宝塔任务计划
等会儿需要在这里设置任务监听
![](assets/20241205_112415_image.png)
#### 新订单监听
一个站点只需要设置一个定时任务,每次访问都会查询一次数据库,并生成新订单数据缓存
![](assets/20241205_112033_image.png)
![](assets/20241205_112517_image.png)
#### 账号监听
**每个账号都需要单独设置一个定时任务**,每次访问都会检索新订单缓存数据,如果存在该账户的新订单,插件就会去查询服务商后台的订单流水,并通知相关收款处理程序
![](assets/20241205_112116_image.png)
![](assets/20241205_113629_image.png)
### 支付测试
只开启一个账号,然后点击`支付测试`,正常支付后,观察是否成功回调,如果不能回调请重新检查配置,或再次详细查看**程序后端控制台主页**的`项目文档`
![](assets/20241205_113835_image.png)
---
## ❤️ 微信/支付宝使用
微信/支付宝生成的收款码,需要挂机监听收款消息,基本情况上面有介绍,因为使用广泛,所以也单独添加进来,可以实现正常收款回调
### 添加账号
收款平台选择`微信支付``支付宝`,收款账号就填写`微信支付``支付宝`的账号,与**聚合码支付**不同,此处填写的账号主要用来做区分,不会登陆后台
![](assets/20241204_164128_image.png)
### 添加收钱码
点击可以查看当前账号的所有收款码
![](assets/20241204_164617_image.png)
点击**添加收款码**后,可以正常填写二维码信息
`微信支付``支付宝`的终端编号是自动生成的,不可手动填写,分别对应当前账号的不同收款通道,支持`个人码``赞赏码`
![](assets/20241204_164650_image.png)
收款样式有两个选项`付款链接``图片地址`,根据实际情况选择
**付款链接:** 将二维码的内容解析成字符串保存,使用时再生成二维码
**图片地址:** 上传二维码图片到服务器或引用http远程图片地址
> `赞赏码`等收款方式,采用的不是标准二维码编码格式,不能解析成文字保存,只能展示图片,建议一般**二维码都解析成文字保存**,前端展示时会根据内容自动再生成二维码,无法解析的就上传图片
---
### 挂机监听收款通知
收款通知信息需要挂机监听,有`手机监听``PC监听`两种手机可以同时监听微信和支付宝PC只能监听微信具体支持的通知和功能在设置账号页面有详细介绍。
#### 🚀️ 手机软件下软(安卓)
码支付使用的是开源工具**短信转发器**`SmsForwarder`来监听收款通知,`SmsForwarder`功能非常强大,喜欢*搞机* 的朋友可以多钻研,这是使用文档[短信转发器](https://gitee.com/pp/SmsForwarder/wikis/pages)
* **开源项目地址:[SmsForwarder](https://gitee.com/pp/SmsForwarder)**
* **发布地址,尽量下载最新版,兼职更多机型:[SmsForwarder](https://gitee.com/pp/SmsForwarder/releases)**
* **网盘地址,访问密码:`pppscn`[SmsForwarder](https://wws.lanzoui.com/b025yl86h)**
![](assets/20241204_171340_image.png)
---
##### 功能配置
找一台不常用的安卓系统手机,下载安装好`SmsForwarder`之后,点开进行配置,同时打开码支付后台**用户中心**页面,查看相关配置参数
![](assets/20241204_175759_image.png)
##### **通用设置**
1. 开启转发应用通知,只针对应用软件才会转发
2. 保活措施全部开启(实际情况看手机环境)
3. 通知栏文案可以自定义,方便自己运维,可参考图片
4. 其他默认即可,也可以参考实际情况自行设置
![](assets/20241204_172346_6c9c6f4d97d5850e7890633edd7e5d5.jpg)
##### **发送通道**
添加发送通道,选择`Webhook`类型
![](assets/20241204_172532_image.png)
**编辑规则**
1. 通道名称自行命名,方便区别就行
2. 请求方式选择`POST`方式
3. Webhook Server 地址**用户中心**查看
4. **消息模版**在**用户中心**查看
5. **Secert**密钥在**用户中心**查看
6. 应签关键字`200`
填写完成点击**保存**即可
![](assets/20241204_172612_image.png)
##### **转发规则**
添加应用转发规则,**微信**和**支付宝**需要分别设置,请注意选择**应用**规则
![](assets/20241204_172656_image.png)
**具体设置**
1. 选择发送通道
2. **匹配字段**选择**多重匹配****匹配的值**去**用户中心**复制,然后粘贴过来
3. 开启**启用自定义模版**,内容填写去码支付后台**账号列表**里复制,粘贴过来
**注意:** 微信支付规则里,第三行的`[空格]`需要替换成真实的` `空格
![](assets/20241204_172718_c313070e899cc93cc2fc9fe25a1ff17.jpg)
![](assets/20241204_175634_image.png)
---
##### **转发日志**
这里可以查看所有的转发记录,点击也可以查看消息详情
![](assets/20241204_172819_2d569802058d4d1b0f92135dcc30469.jpg)
##### **注意事项**
用户中心后台里的配置APP包名不一定与手机应用里的包名一致需要查看一下如果有区别请修改成实际本机显示的APP包名下面有图片演示操作
![](assets/20241204_180022_image.png)
![](assets/20241204_173010_image.png)
![](assets/20241204_173046_image.png)
#### 🚀️ PC软件下载
码支付微信PC监听是老胡使用python写的小工具可以监听电脑桌面打开的窗口和内容信息非侵入微信应用内部版本升级不影响监听。需要将被监听的微信聊天界面单独拖出来成独立窗口。
[PC监听软件下载wxmonitor.zip](assets/20250217_103606_wxmonitor.zip)
##### 配置信息
1. 打开用户中心,添加接口密钥等信息
![](assets/20250217_104137_image.png)
2. 打开账号管理列表,填写通道信息,格式`pid#aid`
![](assets/20250217_104349_image.png)
3. 循环间隔是软件检索窗口通知信息的循环时间一般设置1-3秒均可看电脑性能
4. 监控窗口根据实际情况勾选,哪个窗口需要检索收款通知,就开启哪个,不使用的就不需要开启
![](assets/20250217_105144_image.png)
5. PC监听同时支持个人码、经营码、赞赏码、商户码**手机监听因无法区分个人码和经营码,添加时只能二选一,且只能在`个人码`通道添加**。
![](assets/20250217_105939_image.png)
7. PC监听和手机监听只需要开启一个即可不要同时开启会出现重复通知导致订单异常。
---
# 补充说明
* 该软件仅适用于个人线上免签收款,避免人工审核确认收款的繁琐步骤。请勿用于诈骗、黑灰产业,如有此类行为,后果自负。
* 插件中心目前还在开发中不支持在线安装插件如果有购买插件或定制其他收银平台插件的需求可以联系作者Wechat:**K103516**
# 页面展示
![首页](assets/20241203_153935_image.png)
![安装](assets/20241203_154034_image.png)
![登陆](assets/20241203_154108_image.png)
![后端控制台](assets/20241203_154141_image.png)
![订单管理](assets/20241203_154218_image.png)
![账号管理](assets/20241203_154307_image.png)
![插件管理](assets/20241203_154420_image.png)
![用户中心](assets/20241203_154505_image.png)
![添加账号](assets/20241203_154755_image.png)
![收款码](assets/20241203_154918_image.png)
![订单详情](assets/20241203_155123_image.png)
# 学习交流社群
社群答疑、插件定制、技术交流,添加微信拉群,请备注:**码支付**
微信:**K103516**
<img src="assets/wxqrcode.png" width=50% />
## 感谢赞助
<img src="assets/20250217_111408_1739762042428.jpg" width=35% />
|姓名 | 金额 |
|---|---|
| 知汇学社 | 100 |
| exrock | 100 |
| A筱磊 | 6.66 |
| 云峰 | 8.88 |
| 零零 | 200 |
| 子染 | 88 |
## 友情链接
1. [腾飞博客专业WP子比主题美化定制](https://calfbxtoi72.feishu.cn/docx/DA3ddthzOolQ16xDgeOcHA36nrg)

View File

@@ -20,9 +20,4 @@ class IndexController
View::assign('domain', \request()->domain());
return View::fetch();
}
public function test()
{
return app()->getBasePath();
}
}

View File

@@ -23,11 +23,15 @@ class InstallController
{
// 检查是否已经安装过
if ($this->checkLock()) {
return backMsg(1, '已经安装');
return json(backMsg(1, '已经安装'));
};
// 检查环境
$envCheck = $this->checkEnvironment();
if ($envCheck !== true) {
return json(backMsg(1, $envCheck));
};
// 获取表单提交的数据库配置信息
$dbConfig = $request->post();
// 保存数据库配置信息到配置文件
if ($this->saveDbConfig($dbConfig) === false) {
return json(backMsg(1, '配置保存失败'));
@@ -62,7 +66,22 @@ class InstallController
$this->setLock();
return json(backMsg(0, '安装成功'));
}
private function checkEnvironment()
{
// 检查PHP版本
if (version_compare(PHP_VERSION, '8.0', '<')) {
return 'PHP版本必须大于等于8.0';
}
// 检查文件上传写入权限
if (!is_writable(sys_get_temp_dir())) {
return '文件上传目录没有写入权限';
}
// 检查Fileinfo扩展是否安装
if (!extension_loaded('fileinfo')) {
return 'Fileinfo扩展未安装';
}
return true;
}
private function saveDbConfig($dbConfig)
{
$envPath = app()->getRootPath() . '.env';
@@ -130,7 +149,7 @@ EOT;
`password` varchar(255) NOT NULL DEFAULT '' COMMENT '密码',
`state` tinyint(4) NOT NULL DEFAULT '1' COMMENT '启用',
`pattern` tinyint(4) NOT NULL DEFAULT '1' COMMENT '账号监听模式',
`params` varchar(255) NOT NULL DEFAULT '' COMMENT '自定义查询',
`params` text NOT NULL COMMENT '自定义查询',
`delete_time` timestamp NULL DEFAULT NULL COMMENT '软删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;";

View File

@@ -30,30 +30,30 @@ class PayController
$out_trade_no = Order::where('out_trade_no', $req_data['out_trade_no'])->value('out_trade_no');
if ($out_trade_no) return '订单提交重复';
// 创建新订单
$order_id = Order::createOrder($req_data);
if (!$order_id) return '创建订单失败';
return redirect("/Pay/console/{$order_id}");
$order_info = Order::createOrder($req_data);
if ($order_info['code'] !== 0) return $order_info['msg'];
return redirect("/Pay/console/{$order_info['data']['order_id']}");
}
// api提交订单
public function mapi(Request $request)
{
if (!$request->isPost()) return '请使用POST方式提交';
if (!$request->isPost()) return json(backMsg(0, '请求方式错误'));
$req_data = $request->post();
if (!$req_data) $req_data = $request->get();
if (!$req_data) return '参数错误';
if (!$req_data) return json(backMsg(0, '参数错误'));
// 验证签名
$key = User::where('pid', $req_data['pid'])->where('state', 1)->value('secret_key');
if (!$key) return '用户禁用或不存在';
if (!$key) return json(backMsg(0, '用户禁用或不存在'));
$sign_str = self::getSign($req_data, $key);
if ($req_data['sign'] !== $sign_str) return '签名错误';
if ($req_data['sign'] !== $sign_str) return json(backMsg(0, '签名错误'));
// 检查商户订单
$out_trade_no = Order::where('out_trade_no', $req_data['out_trade_no'])->value('out_trade_no');
if ($out_trade_no) return '订单提交重复';
if ($out_trade_no) return json(backMsg(0, '订单提交重复'));
// 创建新订单
$order_id = Order::createOrder($req_data);
if (!$order_id) return '创建订单失败';
$payurl = $request->domain() . "/Pay/console/{$order_id}";
$info = ['code' => 1, 'msg' => '订单创建成功', 'trade_no' => $order_id, 'qrcode' => $payurl];
$order_info = Order::createOrder($req_data);
if ($order_info['code'] !== 0) return json(backMsg(0, $order_info['msg']));
$payurl = $request->domain() . "/Pay/console/{$order_info['data']['order_id']}";
$info = ['code' => 1, 'msg' => '订单创建成功', 'trade_no' => $order_info['data']['order_id'], 'payurl' => $payurl];
return json($info);
}
// 收银台
@@ -98,6 +98,7 @@ class PayController
$notify['sign'] = $sign;
// 跳转通知URL
$res_return_url = $act_order->return_url . '?' . http_build_query($notify);
if (strpos($act_order->return_url, '?')) $res_return_url = $act_order->return_url . '&' . http_build_query($notify);
// 响应消息
$data['order_id'] = $act_order->order_id;
$data['passtime'] = $passtime > 0 ? $passtime : 0;
@@ -161,7 +162,7 @@ class PayController
// 支付渠道核对
$is_channel = $cids[$order->cid] == $new_order['channel'];
// 金额核对
$is_money = $order->money == $new_order['price'];
$is_money = $order->really_price == $new_order['price'];
// 订单核对
if ($is_payway && $is_channel && $is_money) {
$res = $this->updateOrderState($order, $new_order['order_no']);
@@ -187,7 +188,9 @@ class PayController
$sign = self::getSign($notify, $user_key);
$notify['sign'] = $sign;
// 异步通知
$res_notify = self::getHttpResponse($order->notify_url . '?' . http_build_query($notify));
$notify_url = $order->notify_url . '?' . http_build_query($notify);
if (strpos($order->notify_url, '?')) $notify_url = $order->notify_url . '&' . http_build_query($notify);
$res_notify = self::getHttpResponse($notify_url);
if ($res_notify === 'success') {
return ['order' => $order->order_id, 'code' => 1, 'msg' => 'notify success'];
} else {
@@ -201,9 +204,8 @@ class PayController
$req_pid = $req_info['pid'];
$req_aid = $req_info['aid'];
// 获取订单
$order_path = runtime_path() . '/order.json';
if (!file_exists($order_path)) return json(['code' => 3, 'msg' => '订单文件不存在']);
$new_order = json_decode(file_get_contents($order_path), true);
$new_order = cache('order');
if (!$new_order) return json(['code' => 3, 'msg' => '没有找到新订单缓存']);
// 检测新订单
if ($new_order['code'] !== 1) return json($new_order);
// 订单列表
@@ -221,54 +223,50 @@ class PayController
if ($config === false) return json(['code' => 4, 'msg' => '监听收款配置错误']);
// 登陆账号
$pay_config = ['username' => $config['account'], 'password' => $config['password']];
// 收款查询
$query = $config['query'];
// 配置参数
$params = $config['params'];
// 实例监听客户端
$payclient_name = $config['payclass'];
// 插件类文件是否存在
$payclient_path = root_path() . '/extend/payclient/' . $payclient_name . '.php';
if (!file_exists($payclient_path)) return json(['code' => 5, 'msg' => '监听客户端文件不存在']);
$payclient_path = "\\payclient\\{$payclient_name}";
$Payclient = new $payclient_path($pay_config);
// 获取支付明细
$records = $Payclient->getOrderInfo($query);
if ($records) {
$records = $Payclient->getOrderInfo($params);
if ($records['code'] === 0) {
// 提交收款记录
$upres = $this->payHeart($records, $config);
$upres = $this->payHeart($records['data'], $config);
return $upres;
} else {
return json(['code' => 0, 'msg' => '查询空订单'], 320);
return json(['code' => 0, 'msg' => $records['msg']], 320);
}
}
// [定时任务]监听新订单,生成JSON文件信息
// [定时任务]监听新订单,生成缓存
public function checkOrder($pid = '', $sign = '')
{
if (!($pid && $sign)) {
return '参数错误';
}
if (!($pid && $sign)) return '参数错误';
$is_user = User::checkUser($pid, $sign);
$path = runtime_path() . 'order.json';
if ($is_user) {
$orders = Order::scope('activeOrder')->field('id,pid,aid,cid,patt')->select();
if (!file_exists($path)) {
file_put_contents($path, '[]');
}
$old_info = file_get_contents($path);
$old_info = cache('order');
$num = count($orders);
if ($num > 0) {
$info = ['code' => 1, 'msg' => "{$num}个新订单"];
$order_list = ['code' => 1, 'msg' => "{$num}个新订单", 'orders' => $orders];
if ($old_info !== json_encode($order_list)) {
file_put_contents($path, json_encode($order_list));
if ($old_info !== $order_list) {
cache('order', $order_list);
}
return json($info);
} else {
$info = ['code' => 0, 'msg' => '没有新订单'];
if ($old_info !== json_encode($info, 320)) {
file_put_contents($path, json_encode($info, 320));
if ($old_info !== $info) {
cache('order', $info);
}
return json($info);
}
} else {
$info = ['code' => 2, 'msg' => '签名错误'];
file_put_contents($path, json_encode($info, 320));
return json($info);
}
}
@@ -277,18 +275,18 @@ class PayController
{
$info = $request->post();
$action = isset($info['action']) ? $info['action'] : '';
if ($action === 'mpay') {
$data = json_decode($info['data'], true);
$config = PayAccount::getAccountConfig($data['aid'], $data['pid']);
$payclient_path = "\\payclient\\{$config['payclass']}";
$Payclient = new $payclient_path($info, $config);
$res = $Payclient->notify();
if (is_int($res)) return $res;
$this->payHeart($res, $config);
return 200;
} else {
return 202;
}
if ($action !== 'mpay' && $action !== 'mpaypc') return '非mpay的访问请求';
$data = json_decode($info['data'], true);
if (!is_array($data)) return '通知数据为空';
if (!isset($data['aid']) || !isset($data['pid'])) return 'aid和pid参数错误';
$config = PayAccount::getAccountConfig($data['aid'], $data['pid']);
$payclient_path = "\\payclient\\{$config['payclass']}";
$Payclient = new $payclient_path($info, $config);
if ($action == 'mpay') $res = $Payclient->notify();
if ($action == 'mpaypc') $res = $Payclient->pcNotify();
if ($res['code'] !== 0) return $res['msg'];
$this->payHeart($res['data'], $config);
return 200;
}
// 签名
private static function getSign(array $param = [], string $key = ''): string
@@ -320,7 +318,8 @@ class PayController
'sign_type' => 'MD5',
];
// 添加扩展参数
$notify = array_merge($notify, unserialize($param->param));
// $notify = array_merge($notify, unserialize($param->param));
$notify['param'] = unserialize($param->param);
return $notify;
}
// 请求外部资源

View File

@@ -48,7 +48,9 @@ class OrderController extends BaseController
$sign = self::getSign($notify, $user_key);
$notify['sign'] = $sign;
// 异步通知
$res_notify = self::getHttpResponse($order->notify_url . '?' . http_build_query($notify));
$notify_url = $order->notify_url . '?' . http_build_query($notify);
if (strpos($order->notify_url, '?')) $notify_url = $order->notify_url . '&' . http_build_query($notify);
$res_notify = self::getHttpResponse($notify_url);
if ($res_notify === 'success') {
return json(\backMsg(0, '订单通知成功'));
} else {
@@ -72,7 +74,9 @@ class OrderController extends BaseController
$sign = self::getSign($notify, $user_key);
$notify['sign'] = $sign;
// 异步通知
$res_notify = self::getHttpResponse($order->notify_url . '?' . http_build_query($notify));
$notify_url = $order->notify_url . '?' . http_build_query($notify);
if (strpos($order->notify_url, '?')) $notify_url = $order->notify_url . '&' . http_build_query($notify);
$res_notify = self::getHttpResponse($notify_url);
if ($res_notify === 'success') {
return json(\backMsg(0, '订单通知成功'));
} else {
@@ -156,7 +160,8 @@ class OrderController extends BaseController
'sign_type' => 'MD5',
];
// 添加扩展参数
$notify = array_merge($notify, unserialize($param->param));
// $notify = array_merge($notify, unserialize($param->param));
$notify['param'] = unserialize($param->param);
return $notify;
}
// 请求外部资源

View File

@@ -125,14 +125,27 @@ class PayManageController extends BaseController
public function uploadQrcode()
{
$img = $this->request->file('codeimg');
if (!$img) {
return json(backMsg(1, '请选择要上传的文件'));
}
// 验证文件类型
$allowedTypes = ['image/png', 'image/jpeg', 'image/gif'];
$fileMimeType = $img->getMime();
if (!in_array($fileMimeType, $allowedTypes)) {
return json(backMsg(1, '只允许上传PNG、JPEG或GIF格式的图片'));
}
// 生成唯一文件名
$filename = 'img_' . time() . '_' . uniqid() . '.' . $img->getOriginalExtension();
// 设置文件保存路径
$path = public_path() . '/files/qrcode/';
if (!is_dir($path)) {
mkdir($path, 0777, true);
mkdir($path, 0755, true);
}
$info = $img->move($path, 'img' . time() . '.' . $img->getOriginalExtension());
// 移动文件到指定目录
$info = $img->move($path, $filename);
if ($info) {
$imgpath = '/files/qrcode/';
return json(backMsg(0, '上传成功', ['imgpath' => $imgpath . $info->getFilename()]));
$imgpath = '/files/qrcode/' . $filename;
return json(backMsg(0, '上传成功', ['imgpath' => $imgpath]));
} else {
return json(backMsg(1, '上传失败'));
}
@@ -150,18 +163,18 @@ class PayManageController extends BaseController
// 登陆账号
$pay_config = ['username' => $config['account'], 'password' => $config['password']];
// 收款查询
$query = $config['query'];
$params = $config['params'];
// 实例监听客户端
$payclient_name = $config['payclass'];
$payclient_path = "\\payclient\\{$payclient_name}";
$Payclient = new $payclient_path($pay_config);
// 获取支付明细
$records = $Payclient->getOrderInfo($query);
if ($records) {
$records = $Payclient->getOrderInfo($params);
if ($records['code'] === 0) {
// 收款流水
return json(backMsg(0, '查询成功', $records));
return json(backMsg(0, '查询成功', $records['data']));
} else {
return json(backMsg(1, '查询空订单'));
return json(['code' => 1, 'msg' => $records['msg']]);
}
}
}

View File

@@ -25,42 +25,74 @@ class PluginController extends BaseController
return json(['code' => 1, 'msg' => '无数据记录', 'count' => 0, 'data' => []]);
}
}
// 安装插件
public function installPlugin()
{
$platform = $this->request->post('platform');
if (!$platform) return json(backMsg(1, '请选择插件'));
$intall_info = \Plugin::installPlugin($platform);
if ($intall_info['code'] !== 0) return json(backMsg(1, $intall_info['msg']));
// 需要授权
if ($intall_info['data']['status'] === 0) {
return json(['code' => 0, 'msg' => '请支付', 'state' => 0, 'data' => $intall_info['data']]);
}
$saved = $this->saveNewPluginConfig($intall_info['data']);
if ($saved['code'] !== 0) return json(backMsg(1, $saved['msg']));
return json(['code' => 0, 'msg' => '授权成功', 'state' => 1]);
}
// 更新插件
public function updatePlugin()
{
$platform = $this->request->post('platform');
if (!$platform) return json(backMsg(1, '请选择插件'));
$update_info = \Plugin::updatePlugin($platform);
if ($update_info['code'] !== 0) return json(backMsg(1, $update_info['msg']));
$saved = $this->saveNewPluginConfig($update_info['data']);
if ($saved['code'] !== 0) return json(backMsg(1, $saved['msg']));
return json(['code' => 0, 'msg' => '更新成功']);
}
// 保存全部插件信息
private function saveNewPluginConfig(array $config = [])
{
$plugin_config = $config['config'];
$plugin_auth = $config['authcode'];
$plugin_file = $config['file'];
if (!$this->savePluginFile($plugin_file, $plugin_config)) return backMsg(1, '保存插件文件失败');
if (!$this->saveAuthCode($plugin_auth, $plugin_config)) return backMsg(1, '保存插件授权码失败');
if (!$this->addPlugin($plugin_config)) return backMsg(1, '保存插件配置失败');
return backMsg(0, 'ok');
}
// 卸载插件
public function uninstallPlugin()
{
$platform = $this->request->post('platform');
$classname = $this->request->post('classname');
if (!$platform || !$classname) {
return json(backMsg(1, '请选择插件'));
}
$res1 = $this->delPlugin($platform);
$res2 = $this->delPluginFile($classname);
if ($res1 && $res2) {
return json(backMsg(0, '卸载成功'));
} else {
return json(backMsg(1, '插件不存在'));
}
if (!$platform) return json(backMsg(1, '请选择插件'));
$this->delPluginFile($platform);
$this->delPlugin($platform);
return json(backMsg(0, '卸载成功'));
}
// 添加插件
// 添加或更新插件
public function addPlugin(array $option = [])
{
$keys = ['platform', 'name', 'class_name', 'price', 'describe', 'website', 'state', 'query'];
$keys = ['platform', 'name', 'class_name', 'price', 'describe', 'website', 'helplink', 'version'];
$config = [];
foreach ($option as $key => $value) {
if (in_array($key, $keys)) {
$config[$key] = $value;
}
if (in_array($key, $keys)) $config[$key] = $value;
}
$config['state'] = 1;
$plugin_config = self::getPluginConfig();
$plugin_platform = $config['platform'] ?: '';
foreach ($plugin_config as $value) {
foreach ($plugin_config as $i => $value) {
if ($plugin_platform == $value['platform']) {
return 1; //'插件已存在'
$plugin_config[$i] = $config;
$this->savePluginConfig($plugin_config, '支付插件列表');
return true;
}
}
$plugin_config[] = $config;
$this->savePluginConfig($plugin_config, '支付插件列表');
return 0;
return true;
}
// 删除插件配置
private function delPlugin(string $plugin_name = '')
@@ -70,37 +102,31 @@ class PluginController extends BaseController
foreach ($plugin_config as $i => $value) {
if ($value['platform'] == $plugin_name) {
$index = $i;
break;
}
}
if ($index === null) {
return false; // 插件不存在
}
if ($index === null) return false;
unset($plugin_config[$index]);
$config = array_values($plugin_config);
$this->savePluginConfig($config, '支付插件列表');
return true;
}
// 删除插件类库文件
private function delPluginFile(string $file_name = '')
private function delPluginFile(string $platform = '')
{
$file_name = self::getPluginInfo($platform)['class_name'];
if (!$file_name) return false;
$plugin_path = root_path() . '/extend/payclient/' . $file_name . '.php';
if (file_exists($plugin_path)) {
unlink($plugin_path);
return true;
} else {
return false;
}
if (!file_exists($plugin_path)) return false;
unlink($plugin_path);
return true;
}
// 修改插件
public function setPlugin($platform = '', $option = [])
{
$config = self::getPluginConfig();
if (!$platform) {
return 1; // 请选择插件
}
if (!$option) {
return 2; // 请添加插件配置
}
if (!$platform) return 1;
if (!$option) return 2;
foreach ($config as $index => $options) {
if ($options['platform'] == $platform) {
foreach ($options as $key => $value) {
@@ -117,9 +143,7 @@ class PluginController extends BaseController
public function pluginEnable()
{
$info = $this->request->post();
if ($this->isPluginInstall($info['platform']) == false) {
return json(backMsg(1, '插件未安装'));
}
if (!$this->isPluginInstall($info['platform'])) return json(backMsg(1, '插件未安装'));
$up_res = $this->setPlugin($info['platform'], ['state' => $info['state']]);
if ($up_res) {
return json(backMsg(1, '失败'));
@@ -168,13 +192,30 @@ class PluginController extends BaseController
}
return $info;
}
// 保存授权码
private function saveAuthCode(string $authcode = '', array $config = [])
{
$dir_path = runtime_path() . "auth/";
if (!is_dir($dir_path)) mkdir($dir_path, 755, true);
$auth_path = $dir_path . md5("{$config['platform']}payclient\\{$config['class_name']}") . '.json';
return file_put_contents($auth_path, json_encode(['authcode' => $authcode])) !== false ? true : false;
}
// 保存插件类库文件
private function savePluginFile($file_url = '', array $config = [])
{
if (empty($file_url)) return false;
$file_content = @file_get_contents($file_url);
if ($file_content === false) return false;
$save_dir = root_path() . 'extend/payclient/';
if (!is_dir($save_dir)) mkdir($save_dir, 0755, true);
$save_path = $save_dir . $config['class_name'] . '.php';
return file_put_contents($save_path, $file_content) !== false ? true : false;
}
// 获取插件配置
private static function getPluginConfig(): array
{
$payplugin_path = config_path() . '/extend/payplugin.php';
if (!file_exists($payplugin_path)) {
return [];
}
if (!file_exists($payplugin_path)) return [];
// 加载插件配置
$payplugin_config = require $payplugin_path;
return $payplugin_config;

View File

@@ -13,11 +13,12 @@ class Order extends BaseModel
// 订单有效期
private static $activity_time = 180;
// 新建订单
public static function createOrder($data): string|false
public static function createOrder($data): array
{
$my_time = time();
$channel = self::setChannel($data['pid'], $data['type']);
if (!$channel) return false;
if ($channel['code'] !== 0) return $channel;
$channel = $channel['data'];
$new_order = [
// 订单号
'order_id' => self::createOrderID('H'),
@@ -42,7 +43,7 @@ class Order extends BaseModel
// 设备类型
'device' => isset($data['device']) ? $data['device'] : '',
// 业务扩展参数
'param' => serialize(self::getParams($data)),
'param' => serialize(isset($data['param']) ? $data['param'] : ''),
// 等待/过期0, 支付成功1
'state' => 0,
// 开启监听1, 关闭监听0
@@ -60,9 +61,9 @@ class Order extends BaseModel
];
$res = self::create($new_order);
if ($res->order_id) {
return $res->order_id;
return backMsg(0, 'ok', ['order_id' => $res->order_id]);
} else {
return false;
return backMsg(4, '创建订单记录失败');
}
}
// 查询订单列表
@@ -119,8 +120,9 @@ class Order extends BaseModel
{
// 查询有效收款账户及通道
$aids = PayAccount::where('pid', $pid)->where('state', 1)->column('id');
if (!$aids) return backMsg(1, '用户无可用收款账户');
$channel_infos = PayChannel::whereIn('account_id', $aids)->where('state', 1)->order('last_time', 'asc')->select();
if (!$channel_infos || !$aids) return [];
if ($channel_infos->isEmpty()) return backMsg(2, '用户账户无可用收款码');
// 微信/支付宝收款处理
$channel_info = null;
foreach ($channel_infos as $key => $value) {
@@ -140,25 +142,25 @@ class Order extends BaseModel
break;
}
}
if (!$channel_info) return [];
if (!$channel_info) return backMsg(3, '用户账户无可用收款通道');
// 选取收款通道
$patt = PayAccount::find($channel_info->account_id);
$channel = ['aid' => $channel_info->account_id, 'cid' => $channel_info->id, 'patt' => $patt->getData('pattern')];
PayChannel::update(['last_time' => self::getFormatTime(), 'id' => $channel['cid']]);
return $channel;
return backMsg(0, 'ok', $channel);
}
// 获取扩展参数数组
private static function getParams(array $data): array
{
$keys = ['pid', 'type', 'out_trade_no', 'notify_url', 'return_url', 'name', 'money', 'sign', 'sign_type'];
$params = [];
foreach ($data as $key => $value) {
if (!in_array($key, $keys)) {
$params[$key] = $value;
}
}
return $params;
}
// private static function getParams(array $data): array
// {
// $keys = ['pid', 'type', 'out_trade_no', 'notify_url', 'return_url', 'name', 'money', 'sign', 'sign_type'];
// $params = [];
// foreach ($data as $key => $value) {
// if (!in_array($key, $keys)) {
// $params[$key] = $value;
// }
// }
// return $params;
// }
// 检查金额
private static function checkMoney($money, $type, $aid, $cid): float
{

View File

@@ -34,7 +34,6 @@ class PayAccount extends BaseModel
$platform = PluginController::getPluginInfo($aid_info->getData('platform'));
// 查询参数
$params = json_decode($aid_info->params, true);
$query = array_merge($platform['query'], $params);
if ($aid_info && $platform) {
$config = [
'pid' => $aid_info->pid,
@@ -48,8 +47,8 @@ class PayAccount extends BaseModel
'account' => $aid_info->account,
// 密码
'password' => $aid_info->password,
// 订单查询参数配置
'query' => $query,
// 配置参数
'params' => $params,
];
if ($pid !== null) {
$pid_info = User::where('pid', $pid)->find();

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

BIN
assets/wxqrcode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

View File

@@ -9,46 +9,35 @@ return array (
'platform' => 'wxpay',
'name' => '微信支付',
'class_name' => 'WxPay',
'price' => '0.00',
'price' => NULL,
'describe' => '支持微信个人收款码、赞赏码、经营码、商家码收款,监听回调',
'website' => 'https://weixin.qq.com/',
'website' => 'https://weixin.qq.com',
'helplink' => '',
'version' => '1.0',
'state' => 1,
'query' =>
array (
),
),
1 =>
array (
'platform' => 'alipay',
'name' => '支付宝',
'class_name' => 'AliPay',
'price' => '0.00',
'describe' => '支持支付宝个人收款码、经营码收款,监听回调',
'website' => 'https://www.alipay.com/',
'state' => 1,
'query' =>
array (
),
),
2 =>
array (
'platform' => 'sqbpay',
'name' => '收钱吧',
'class_name' => 'ShouQianBa',
'price' => '0.00',
'price' => NULL,
'describe' => '主流移动支付全能收 信用卡,花呗都能用,生意帮手收钱吧,移动收款就用它!',
'website' => 'https://www.shouqianba.com/',
'website' => 'https://www.shouqianba.com',
'helplink' => '',
'version' => '1.0',
'state' => 1,
),
2 =>
array (
'platform' => 'alipay',
'name' => '支付宝',
'class_name' => 'AliPay',
'price' => NULL,
'describe' => '支持支付宝个人收款码、经营码收款,监听回调',
'website' => 'https://www.alipay.com',
'helplink' => '',
'version' => '1.0',
'state' => 1,
'query' =>
array (
'date_end' => NULL,
'date_start' => NULL,
'page' => 1,
'page_size' => 10,
'upayQueryType' => 0,
'status' => '2000',
'store_sn' => '',
'type' => '30',
),
),
);

1
extend/.gitignore vendored
View File

@@ -1 +0,0 @@
payclient

View File

@@ -23,6 +23,7 @@ class Plugin
// 获取已安装插件
public static function getInstall(array $local_plugin = []): array
{
if (empty($local_plugin)) return [];
foreach ($local_plugin as $key => $value) {
$local_plugin[$key]['install'] = true;
}
@@ -33,8 +34,10 @@ class Plugin
{
$uninstall_plugin = [];
$install = [];
foreach ($local_plugin as $e_val) {
$install[] = $e_val['platform'];
if (!empty($local_plugin)) {
foreach ($local_plugin as $e_val) {
$install[] = $e_val['platform'];
}
}
foreach ($app_plugin as $i_val) {
if (in_array($i_val['platform'], $install)) {
@@ -49,37 +52,66 @@ class Plugin
// 获取平台所有支持插件
public static function getAllPlugin(): array
{
$app_plugin = self::getHttpResponse(self::$siteUrl . '/mpay/getplugins');
return json_decode($app_plugin, true);
$app_plugin = cache('app_plugin');
if ($app_plugin) return $app_plugin;
$app_plugin = self::getHttpResponse(self::$siteUrl . '/MpayApi', ['action' => 'getPluginList']);
$info = json_decode($app_plugin, true);
if ($info['code'] === 0) cache('app_plugin', $info['data'], 36000);
return $info['data'];
}
// 获取通知消息
public static function getNotifyMessage(): array
{
$message = self::getHttpResponse(self::$siteUrl . '/mpay/message');
$message = cache('message');
if ($message) return json_decode($message, true);
$message = self::getHttpResponse(self::$siteUrl . '/MpayApi', ['action' => 'message']);
cache('message', $message, 36000);
return json_decode($message, true);
}
// 安装插件
public static function installPlugin($platform): array
{
$res = self::getHttpResponse(self::$siteUrl . '/MpayApi', ['action' => 'installPlugin', 'platform' => $platform, 'host' => parse_url(request()->domain(), PHP_URL_HOST)]);
// halt($res);
return json_decode($res, true);
}
// 更新插件
public static function updatePlugin($platform): array
{
$res = self::getHttpResponse(self::$siteUrl . '/MpayApi', ['action' => 'updatePlugin', 'platform' => $platform, 'host' => parse_url(request()->domain(), PHP_URL_HOST)]);
return json_decode($res, true);
}
// 请求外部资源
private static function getHttpResponse($url, $header = [], $post = null, $timeout = 10)
private static function getHttpResponse($url, $post = null, $header = [], $timeout = 10)
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
if ($header) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
} else {
$httpheader[] = "Accept: */*";
$httpheader[] = "Accept-Language: zh-CN,zh;q=0.9";
$httpheader[] = "Connection: close";
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader);
}
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$httpheader = [
"Accept: application/json",
"Accept-Language: zh-CN,zh;q=0.9",
"Connection: close",
"mpayAgent: your_mpay_agent_identifier"
];
$httpheader = array_merge($httpheader, $header);
if ($post) {
if (!is_string($post)) $post = json_encode($post);
$httpheader[] = "Content-Type: application/json";
$httpheader[] = "Content-Length: " . strlen($post);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$response = curl_exec($ch);
// 检查 cURL 请求是否出错
if ($response === false) {
$error = curl_error($ch);
curl_close($ch);
throw new \RuntimeException("cURL error: $error");
}
curl_close($ch);
return $response;
}

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

2
runtime/.gitignore vendored
View File

@@ -1,2 +0,0 @@
*
!.gitignore

3
vendor/.gitignore vendored
View File

@@ -1,3 +0,0 @@
*
!.gitignore
!vendor.zip

View File

@@ -126,10 +126,11 @@
const field = data.field;
(async () => {
const saveRes = await fetch('/install/install', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(field) });
if (saveRes.status != 200) { layer.msg('保存数据库配置失败'); return; }
const saveJson = await saveRes.json();
if (saveJson.code == 0) {
const initRes = await fetch('/install/init', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(field) });
if (initRes.status != 200) { layer.msg('初始化失败,请检查配置'); return; }
if (initRes.status != 200) { layer.msg('初始化数据失败,请检查配置'); return; }
const initJson = await initRes.json();
if (initJson.code == 0) {
layer.msg(initJson.msg, {

View File

@@ -349,7 +349,8 @@
type: 2,
title: '订单详细',
shade: 0.1,
area: [common.isModile() ? '100%' : '500px', common.isModile() ? '100%' : '800px'],
offset: '10px',
area: [common.isModile() ? '100%' : '500px', common.isModile() ? '100%' : '640px'],
content: `/Order/showOrder?id=${id}`,
});
}

View File

@@ -150,6 +150,35 @@
</div>
<script src="/component/layui/layui.js"></script>
<script src="/static/js/awesome-qr.min.js"></script>
<script>
function detectBrowserEnvironment() {
const userAgent = navigator.userAgent.toLowerCase();
let environment = 'other';
if (userAgent.includes('micromessenger')) {
if (userAgent.includes('miniprogram')) {
environment = 'wxapp'; // 微信小程序
} else if (userAgent.includes('android') || userAgent.includes('iphone') || userAgent.includes('ipad')) {
environment = 'wxphone'; // 手机微信
} else {
environment = 'wxpc'; // PC微信
}
} else if (userAgent.includes('aliapp') || userAgent.includes('alipayclient')) {
if (userAgent.includes('android') || userAgent.includes('iphone') || userAgent.includes('ipad')) {
environment = 'aliphone'; // 手机支付宝
} else {
environment = 'alipc'; // PC支付宝
}
} else if (userAgent.includes('android') || userAgent.includes('iphone') || userAgent.includes('ipad')) {
// 先判断是否是已知的手机APP内置浏览器如果不是则认为是手机浏览器
if (!userAgent.includes('micromessenger') && !userAgent.includes('aliapp') && !userAgent.includes('qq')) {
environment = 'phone'; // 手机浏览器
}
} else {
environment = 'pc'; // 剩下的情况认为是PC浏览器
}
return environment;
}
</script>
<script>
const payCode = '<?php echo htmlentities($payUrl); ?>';
const codeType = '<?php echo htmlentities($code_type); ?>';
@@ -247,6 +276,14 @@
return info;
}
/* <?php } ?> */
// 环境判断
const environment = detectBrowserEnvironment();
if (payType === 'wxpay' && environment === 'aliphone') {
layer.alert('请使用微信打开此页面');
} else if (payType === 'alipay' && environment === 'wxphone') {
layer.alert('请使用支付宝打开此页面');
}
// 生成二维码
async function getQrcode(text, QR) {
const qrcodeUrl = await new Promise((resolve) => {

View File

@@ -39,10 +39,10 @@
lay-filter="scanning" class="layui-input" data-type="">
</div>
</div>
</div>
<div class="main-container">
<div class="layui-text">
<p>终端编号如何填写,<strong><a href="https://f0bmwzqjtq2.feishu.cn/docx/HBVrdrsACo36bzxUCSPcjOBNnyb?from=from_copylink" target="_blank"><strong>请查看文档</strong></a></p>
<div class="layui-form-item">
<blockquote class="layui-elem-quote">
<p>首次添加,不知道终端编号可以先空着,后面再查询填写。查询终端编号,<strong><a class="layui-font-blue" href="https://f0bmwzqjtq2.feishu.cn/docx/HBVrdrsACo36bzxUCSPcjOBNnyb?from=from_copylink" target="_blank"><strong>请查看文档</strong></a></p>
</blockquote>
</div>
</div>
</div>

View File

@@ -30,12 +30,12 @@
<!-- <?php if ($platform == 'wxpay') { ?> -->
<option value="wxpay1">个人码</option>
<option value="wxpay2">赞赏码</option>
<!-- <option value="wxpay3">经营码</option> -->
<option value="wxpay3">经营码</option>
<option value="wxpay4">商家码</option>
<!-- <?php } ?> -->
<!-- <?php if ($platform == 'alipay') { ?> -->
<option value="alipay1">收钱码</option>
<!-- <option value="alipay2">经营码</option> -->
<option value="alipay2">经营码</option>
<!-- <?php } ?> -->
</select>
</div>
@@ -57,6 +57,18 @@
lay-filter="scanning" class="layui-input" data-type="">
</div>
</div>
<div class="layui-form-item">
<blockquote class="layui-elem-quote">
<!-- <?php if ($platform == 'wxpay') { ?> -->
<p><strong>手机监控:</strong>微信个人码与经营码只能二选一,不能同时添加,商家码监听需要关注“微信收款商业版”公众号,赞赏码正常添加。</p>
<p><strong>电脑监控:</strong>所有收款码均可监控,需要将收款通知聊天窗口单独拖出来</p>
<!-- <?php } ?> -->
<!-- <?php if ($platform == 'alipay') { ?> -->
<p><strong>手机监控:</strong>支付宝收钱码与经营码通知无法做区分,只能二选一,且只能通过收钱码选项添加</p>
<p><strong>电脑监控:</strong>暂不支持PC端监控可以选择支付宝账单方式监听收款</p>
<!-- <?php } ?> -->
</blockquote>
</div>
</div>
</div>
<div class="bottom">

View File

@@ -57,23 +57,45 @@
<a href="javascript:;" class="layui-font-green" lay-event="pluginInstall">立即安装</a>
{{# } }}
</script>
<script type="text/html" id="plugin-pay">
<div class="layui-bg-gray layui-padding-3" style="height: 100%;">
<div class="layui-card" style="max-width: 320px;min-width: 240px;margin: 0 auto;text-align: center;">
<div class="layui-card-header">
<strong>请使用{{- d.paytype==='alipay'?'<span class="layui-font-blue">支付宝</span>':'<span class="layui-font-green">微信支付</span>' }}付款</strong>
</div>
<div class="layui-card-body">
<div class="price"><p><span class="layui-font-red layui-font-18">{{= d.money }}</span></p><p>{{= d.name }}</p><p>{{= d.orderid }}</p></div>
<div class="layui-padding-3 paycode"><img id="qrcode" src="/static/img/loading.gif" width="100%" /></div>
<div class="note">
<p>支付<span class="layui-font-red">{{= d.money }}</span></p>
<p><span class="layui-font-blue">{{= d.closetime }}</span> </p>
<p>支付完成点击确认支付过期请不要支付</p>
</div>
</div>
</div>
</div>
</script>
<script src="/component/layui/layui.js"></script>
<script src="/component/pear/pear.js"></script>
<script src="/static/js/awesome-qr.min.js"></script>
<script>
layui.use(['layer', 'form', 'common', 'layer'], function () {
const QR = AwesomeQR.AwesomeQR;
layui.use(['layer', 'form', 'common', 'layer', 'laytpl'], function () {
let table = layui.table;
let layer = layui.layer;
let form = layui.form;
let common = layui.common;
let util = layui.util;
let laytpl = layui.laytpl;
const cols = [[
{ title: '收款平台名称', field: 'name', align: 'center', maxWidth: 180 },
{ title: '官网', field: 'name', align: 'left', maxWidth: 220, templet: `<div><a href="{{= d.website }}" class="layui-font-green" target="_blank">{{= d.website }}</a></div>` },
{ title: '说明', field: 'describe', align: 'left' },
{ title: '价格', field: 'price', align: 'center', maxWidth: 180, templet: `<div>{{= d.price==0 ? '免费' : d.price }}</div>` },
{ title: '状态', field: 'state', align: 'center', maxWidth: 180, templet: `#plugin-state` },
{ title: '操作', align: 'center', maxWidth: 180, fixed: 'right', templet: `#plugin-action` },
{ title: '价格', field: 'price', align: 'center', width: 120, templet: `<div>{{= d.price==0 ? '免费' : d.price }}</div>` },
{ title: '状态', field: 'state', align: 'center', width: 120, templet: `#plugin-state` },
{ title: '教程', field: 'helplink', align: 'center', width: 120, templet: `<div><a href="{{= d.helplink }}" class="layui-font-green" target="_blank">使用文档</a></div>` },
{ title: '操作', align: 'center', width: 180, fixed: 'right', templet: `#plugin-action` },
]]
table.render({
@@ -101,17 +123,13 @@
table.on('tool(plugin-table)', function (obj) {
const id = obj.data.id;
if (obj.event === 'pluginInstall') {
layer.msg('功能开发中,有需要请联系作者');
plugin.install({ platform: obj.data.platform });
} else if (obj.event === 'pluginUpdate') {
layer.msg('功能开发中,有需要请联系作者');
plugin.update({ platform: obj.data.platform });
} else if (obj.event === 'pluginUninstall') {
layer.confirm('卸载之后无法恢复,确认吗?', { icon: 3, title: '卸载插件' }, function (index) {
layer.close(index);
config = {
platform: obj.data.platform,
classname: obj.data.class_name
}
plugin.uninstall(config);
plugin.uninstall({ platform: obj.data.platform });
});
}
});
@@ -155,19 +173,125 @@
}
// 卸载插件
plugin.uninstall = async (config) => {
const load = layer.load(2);
const res = await fetch('/api/Plugin/uninstallPlugin', { method: 'post', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config) });
if (res.status !== 200) {
layer.msg('请求失败,请重试!', { tips: 2, time: 1200 });
layer.close(load);
return false;
}
const rec_info = await res.json();
if (rec_info.code === 0) {
layer.msg(rec_info.msg, { icon: 1, time: 1200 }, () => { table.reload('plugin-table'); });
layer.close(load);
return false;
} else {
layer.msg(rec_info.msg, { icon: 2, time: 1200 });
layer.close(load);
return false;
}
}
// 安装插件
plugin.install = async (config, step = 0) => {
// 加载中
const load = layer.load(2);
const res = await fetch('/api/Plugin/installPlugin', { method: 'post', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config) });
if (res.status !== 200) {
layer.close(load);
layer.msg('请求失败,请重试!', { tips: 2, time: 1200 });
return false;
}
const rec_info = await res.json();
// 错误信息
if (rec_info.code !== 0) {
layer.close(load);
layer.msg(rec_info.msg, { icon: 2, time: 1200 });
return false;
}
const info = rec_info.data;
if (rec_info.state === 0) {
plugin.pay(info, config.platform, step);
layer.close(load);
return false;
} else {
// 安装成功
layer.close(load);
layer.msg(rec_info.msg, { icon: 1, time: 1200 }, () => { table.reload('plugin-table'); });
return false;
}
}
// 更新插件
plugin.update = async (config) => {
const load = layer.load(2);
const res = await fetch('/api/Plugin/updatePlugin', { method: 'post', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config) });
if (res.status !== 200) {
layer.msg('请求失败,请重试!', { tips: 2, time: 1200 });
layer.close(load);
return false;
}
const rec_info = await res.json();
if (rec_info.code === 0) {
layer.close(load);
layer.msg(rec_info.msg, { icon: 1, time: 1200 }, () => { table.reload('plugin-table'); });
return false;
} else {
layer.close(load);
layer.msg(rec_info.msg, { icon: 2, time: 1200 });
return false;
}
}
// 支付费用
plugin.pay = (config, platform, step) => {
if (step == 0) {
plugin.payPage(config, platform);
} else {
layer.alert('支付失败,请重试!', { icon: 0, title: '提示' }, function (index) {
layer.close(index);
plugin.payPage(config, platform);
});
}
}
plugin.payPage = async (config, platform) => {
layer.open({
type: 1,
title: '支付费用',
area: [common.isModile() ? '100%' : '360px', common.isModile() ? '100%' : 'auto'],
content: laytpl(document.getElementById('plugin-pay').innerHTML).render(config),
offset: '16px',
anim: 1,
btn: ['确认支付', '取消'],
success: function () {
plugin.setQrcode(config.codetype, config.payurl);
},
yes: (index) => {
layer.close(index);
layer.load(2);
plugin.install({ platform: platform }, 1);
},
btn2: (index) => {
layer.close(index);
}
})
}
// 设置二维码
plugin.setQrcode = async (codeType, payCode) => {
if (codeType == 0) {
document.getElementById('qrcode').src = await getQrcode(payCode);
} else {
document.getElementById('qrcode').src = payCode;
}
}
});
// 生成二维码
async function getQrcode(text) {
const qrcodeUrl = await new Promise((resolve) => {
new QR({
text: text,
size: 500,
}).draw().then((dataURL) => { resolve(dataURL); });
})
return qrcodeUrl;
}
</script>
</body>

View File

@@ -13,7 +13,7 @@
top: 20px;
z-index: 999;
}
label {
display: flex !important;
align-items: center;
@@ -47,7 +47,7 @@
<span id="yiyan"></span>
</div>
</div>
<div class="layui-card">
<div class="layui-card-header">支付API配置<span class="layui-font-gray"> · 易支付接口标准</span></div>
<div class="layui-card-body">
@@ -90,7 +90,7 @@
</div>
</div>
</div>
<div class="layui-card">
<div class="layui-card-header">聚合码定时任务<span class="layui-font-gray"> · 监控有效期订单</span></div>
<div class="layui-card-body">
@@ -112,7 +112,7 @@
</div>
</div>
<div class="layui-col-md6">
<div class="layui-card">
<div class="layui-card-header">收款码监控配置<span class="layui-font-gray"> · SmsForwarder</span></div>
<div class="layui-card-body">
@@ -194,9 +194,9 @@
<div class="layui-input-block">
<div class="layui-form-mid layui-elip"
style="margin-left: 10px;color: #5f5f5f;float: none;">
<?php echo '并且 是 APP包名 相等 com.eg.android.AlipayGphone<br />并且 是 通知标题 包含 成功收款' ?>
<?php echo '并且 是 APP包名 相等 com.eg.android.AlipayGphone<br />并且 是 通知标题 包含 元<br />[空格]或者 是 通知内容 包含 元' ?>
<a href="javascript:;" lay-on="copyinfo"
data-info='<?php echo "并且 是 APP包名 相等 com.eg.android.AlipayGphone\n并且 是 通知标题 包含 成功收款" ?>'
data-info='<?php echo "并且 是 APP包名 相等 com.eg.android.AlipayGphone\n并且 是 通知标题 包含 元\n[空格]或者 是 通知内容 包含 元" ?>'
style="float: right;" title="复制"><span
class="icon pear-icon pear-icon-survey"></span></a>
</div>
@@ -207,7 +207,7 @@
</fieldset>
</div>
</div>
</div>
</div>
</div>