Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7757d51ae3 | ||
|
|
40f07d9023 | ||
|
|
16513960ad | ||
|
|
a9df43fd04 |
93
README.md
@@ -1,6 +1,3 @@
|
||||
|
||||
|
||||
|
||||
# 码支付
|
||||
|
||||

|
||||
@@ -47,7 +44,7 @@
|
||||
|
||||
* 平台容易因为资质问题导致关站;
|
||||
* 收取的手续费价格偏高;
|
||||
* 个人码在微信H5环境无法长按识别付款,能只技术PC端相机扫码付款。
|
||||
* 个人码在微信H5环境无法长按识别付款,只能通过PC端,相机扫码付款。
|
||||
|
||||
#### 码支付(mpay)
|
||||
|
||||
@@ -85,6 +82,7 @@
|
||||
当用户付款成功,并且后台检测到收款成功消息后,收钱台就会提示收款成功,并最终确认收款。
|
||||
|
||||
> 只有存在新订单时,且该订单与当前收款账号一致时,码支付后台才会主动登陆该账号,查询收款流水,减少频繁查询导致的可能风险
|
||||
|
||||
> 另外,在账号设置里也有两个模式可选,`单次监听`和`连续监听`,根据业务场景可以自行选择,具体使用,下面有介绍
|
||||
|
||||
## 开源声明
|
||||
@@ -99,28 +97,83 @@
|
||||
|
||||
### 技术架构
|
||||
|
||||
须用`Thinkphp8`框架,前端UI使用`Layui 2.9`+`PearAdmin`后台
|
||||
使用`Thinkphp8`框架,PHP版本 > 8.0(推荐8.2),前端UI使用`Layui 2.9`+`PearAdmin`后台
|
||||
|
||||
## 安装和使用
|
||||
|
||||
### 全新安装
|
||||
|
||||
以下演示基于**云服务器**环境+**宝塔面板**安装,云服务器购买可以去阿里云、腾讯云等平台,宝塔面板安装教程参考[宝塔面板安装教程](https://www.bt.cn/new/download.html)
|
||||
|
||||
#### 源码下载
|
||||
|
||||
点击下载 [码支付 v1.0.0版本](https://gitee.com/technical-laohu/mpay/releases/tag/v1.0.0)
|
||||
更多版本请关注发行版更新记录
|
||||
|
||||
#### 安装配置
|
||||
|
||||
以**宝塔面板**示例,其他服务器管理面板可以参考
|
||||
|
||||
##### 新建站点与数据库
|
||||
通过宝塔面板登陆管理后台,新建PHP站点和数据库,并确认创建
|
||||
|
||||
#### 仿静态配置
|
||||

|
||||
|
||||
在网站列表页面,点击创建的网站的根目录,进入文件管理
|
||||
|
||||

|
||||
|
||||
文件夹里面有一些默认文件,不用管他
|
||||
|
||||

|
||||
点击上传文件,将源码压缩包上传到该文件夹,并解压到前文件夹
|
||||
|
||||

|
||||
**将`mpay`文件夹里面的所有文件,复制到当前根目录下**,返回网站列表管理页面
|
||||
|
||||
> 注意,压缩包文件打包的是一个名为`mpay`的文件夹,需要将代码文件夹里面的所有文件复制出来,放到创建的网站根目录下,
|
||||
|
||||
#### 运行目录&仿静态 配置
|
||||
|
||||
点击网站名,进入网站配置设置页面
|
||||
|
||||

|
||||
|
||||
选择**网站目录**,运行目录选择`public`,保存
|
||||
|
||||

|
||||
|
||||
选择**伪静态**,模版选择`thinkphp`,即可自动填写,保存
|
||||
|
||||

|
||||
|
||||
#### 安装步骤
|
||||
|
||||
在浏览器输入`http://你的域名/install`,进入程序安装界面,按照提示进行填写提交
|
||||
|
||||

|
||||
|
||||
数据库配置相关信息,在服务器管理面板里查找
|
||||
|
||||

|
||||
|
||||
### 聚合码使用
|
||||
|
||||
如果本身就有聚合码收钱码最好,没有就需要提前去各收银服务商申请,申请也不复杂
|
||||
|
||||
#### 申请收款码
|
||||
|
||||
以下列出一些常见收款服务平台,可以按需申请,个人直接申请小微商户即可
|
||||
|
||||
| 平台 | 官网 |
|
||||
|--------|--------------------------------------|
|
||||
| 收钱吧 | https://www.shouqianba.com/ |
|
||||
| 小Y经营 | https://xym.ysepay.com/ |
|
||||
| 码钱 | https://m.hkrt.cn/ |
|
||||
| 拉卡拉 | https://customer.lakala.com/ |
|
||||
| 盛付通 | https://b.shengpay.com/ |
|
||||
|
||||
> 申请可以去官方平台注册账号等客服电话,或者在社群里询问(有很多人有代办资质),实在找不到的,可以去淘宝上的官方店买个二维码卡牌贴纸,然后询问客服如何开通账号就行,会有专员联系你开通。
|
||||
|
||||
#### 安装插件
|
||||
|
||||
#### 添加账号
|
||||
@@ -135,10 +188,10 @@
|
||||
|
||||
#### 支付测试
|
||||
|
||||
#### 服务商支持
|
||||
|
||||
### 微信/支付宝使用
|
||||
|
||||
微信/支付宝生成的收款码,需要挂机监听收款消息,基本情况上面有介绍,因为使用广泛,所以也单独添加进来,可以实现正常收款回调
|
||||
|
||||
#### 添加账号
|
||||
|
||||
#### 添加收钱码
|
||||
@@ -155,17 +208,25 @@
|
||||
|
||||
### 页面展示
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ class ConsoleController extends BaseController
|
||||
// 后台主页
|
||||
public function index()
|
||||
{
|
||||
View::assign('version', 'v1.0.1');
|
||||
return View::fetch();
|
||||
}
|
||||
// 管理菜单
|
||||
|
||||
@@ -53,7 +53,7 @@ class InstallController
|
||||
{
|
||||
$envPath = app()->getRootPath() . '.env';
|
||||
$envContent = <<<EOT
|
||||
APP_DEBUG = true
|
||||
APP_DEBUG = false
|
||||
|
||||
DB_TYPE = mysql
|
||||
DB_HOST = {$dbConfig['host']}
|
||||
|
||||
@@ -143,7 +143,22 @@ class PayManageController extends BaseController
|
||||
unlink($path);
|
||||
}
|
||||
}
|
||||
|
||||
// 上传二维码图片
|
||||
public function uploadQrcode()
|
||||
{
|
||||
$img = $this->request->file('codeimg');
|
||||
$path = public_path() . '/files/qrcode/';
|
||||
if (!is_dir($path)) {
|
||||
mkdir($path, 0777, true);
|
||||
}
|
||||
$info = $img->move($path, 'img' . time() . '.' . $img->getOriginalExtension());
|
||||
if ($info) {
|
||||
$imgpath = '/files/qrcode/';
|
||||
return json(backMsg(0, '上传成功', ['imgpath' => $imgpath . $info->getFilename()]));
|
||||
} else {
|
||||
return json(backMsg(1, '上传失败'));
|
||||
}
|
||||
}
|
||||
// 生成账号配置
|
||||
private function createAccountConfig($acc)
|
||||
{
|
||||
|
||||
|
Before Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 123 KiB |
BIN
assets/20241203_153935_image.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
assets/20241203_154034_image.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
assets/20241203_154108_image.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
assets/20241203_154141_image.png
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
assets/20241203_154218_image.png
Normal file
|
After Width: | Height: | Size: 139 KiB |
BIN
assets/20241203_154307_image.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
assets/20241203_154420_image.png
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
assets/20241203_154505_image.png
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
assets/20241203_154755_image.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
assets/20241203_154918_image.png
Normal file
|
After Width: | Height: | Size: 123 KiB |
BIN
assets/20241203_155123_image.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
assets/20241203_161723_image.png
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
assets/20241203_162102_image.png
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
assets/20241203_162231_image.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
assets/20241203_162646_image.png
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
assets/20241203_163259_image.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
assets/20241203_163507_image.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
assets/20241203_164321_image.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
assets/20241203_165327_image.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
assets/20241203_165507_image.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 47 KiB |
@@ -1 +0,0 @@
|
||||
1732883162
|
||||
@@ -82,7 +82,7 @@
|
||||
<div class="layui-side-scroll">
|
||||
<div id="sideMenu"></div>
|
||||
</div>
|
||||
<div id="version"><p>版本号:v1.0.0</p><p>by techhaha</p></div>
|
||||
<div id="version"><p>版本号:<?php echo $version ?></p><p>by techhaha</p></div>
|
||||
</div>
|
||||
<!-- 视 图 页 面 -->
|
||||
<div class="layui-body">
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">收款样式</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="type">
|
||||
<select name="type" lay-filter="type">
|
||||
<option value="">请选择</option>
|
||||
<option value="0">付款链接</option>
|
||||
<option value="1">图片地址</option>
|
||||
@@ -36,7 +36,7 @@
|
||||
<label class="layui-form-label">收款码</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="qrcode" autocomplete="off" lay-affix="upload-drag"
|
||||
lay-filter="scanning" class="layui-input">
|
||||
lay-filter="scanning" class="layui-input" data-type="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -61,10 +61,34 @@
|
||||
layui.use(['form'], function () {
|
||||
let form = layui.form;
|
||||
|
||||
form.on('input-affix(scanning)', function () {
|
||||
window.open('https://cli.im/deqr', '_blank');
|
||||
form.on('input-affix(scanning)', function (obj) {
|
||||
const type = obj.elem.getAttribute('data-type');
|
||||
if (type === '') {
|
||||
layer.msg('请先选择二维码类型!', { tips: 2, time: 1200 });
|
||||
return false;
|
||||
}
|
||||
if (type === 'img') {
|
||||
uploadCodeImg(obj.elem);
|
||||
return false;
|
||||
}
|
||||
if (type === 'link') {
|
||||
window.open('https://cli.im/deqr', '_blank');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
form.on('select(type)', function (data) {
|
||||
const form = data.elem.form;
|
||||
const value = data.value;
|
||||
const inuptelem = form.querySelector('input[name="qrcode"]');
|
||||
if (value === '0') {
|
||||
inuptelem.setAttribute('data-type', 'link');
|
||||
return false;
|
||||
}
|
||||
if (value === '1') {
|
||||
inuptelem.setAttribute('data-type', 'img');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
form.on('submit(save)', function (obj) {
|
||||
let field = obj.field;
|
||||
field.account_id = '<?php echo $aid ?>';
|
||||
@@ -88,6 +112,40 @@
|
||||
})()
|
||||
return false;
|
||||
});
|
||||
// 上传二维码图片
|
||||
async function uploadCodeImg(elem) {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = 'image/*';
|
||||
input.onchange = async (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
const formData = new FormData();
|
||||
formData.append('codeimg', file);
|
||||
try {
|
||||
const response = await fetch('/api/PayManage/uploadQrcode', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
if (result.code === 0) {
|
||||
layer.msg(result.msg, { icon: 1, time: 1200 });
|
||||
elem.value = result.data.imgpath;
|
||||
return result.data.imgpath;
|
||||
} else {
|
||||
layer.msg('上传失败:' + result.message, { tips: 2, time: 1200 });
|
||||
}
|
||||
} else {
|
||||
layer.msg('上传失败:服务器错误', { tips: 2, time: 1200 });
|
||||
}
|
||||
} catch (error) {
|
||||
layer.msg('上传失败:' + error.message, { tips: 2, time: 1200 });
|
||||
}
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">收款样式</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="type">
|
||||
<select name="type" lay-filter="type">
|
||||
<option value="">请选择</option>
|
||||
<option value="0">付款链接</option>
|
||||
<option value="1">图片地址</option>
|
||||
@@ -54,7 +54,7 @@
|
||||
<label class="layui-form-label">收款码</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="qrcode" autocomplete="off" lay-affix="upload-drag"
|
||||
lay-filter="scanning" class="layui-input">
|
||||
lay-filter="scanning" class="layui-input" data-type="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -79,9 +79,35 @@
|
||||
layui.use(['form'], function () {
|
||||
let form = layui.form;
|
||||
|
||||
form.on('input-affix(scanning)', function () {
|
||||
window.open('https://cli.im/deqr', '_blank');
|
||||
form.on('input-affix(scanning)', function (obj) {
|
||||
const type = obj.elem.getAttribute('data-type');
|
||||
if (type === '') {
|
||||
layer.msg('请先选择二维码类型!', { tips: 2, time: 1200 });
|
||||
return false;
|
||||
}
|
||||
if (type === 'img') {
|
||||
uploadCodeImg(obj.elem);
|
||||
return false;
|
||||
}
|
||||
if (type === 'link') {
|
||||
window.open('https://cli.im/deqr', '_blank');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
form.on('select(type)', function (data) {
|
||||
const form = data.elem.form;
|
||||
const value = data.value;
|
||||
const inuptelem = form.querySelector('input[name="qrcode"]');
|
||||
if (value === '0') {
|
||||
inuptelem.setAttribute('data-type', 'link');
|
||||
return false;
|
||||
}
|
||||
if (value === '1') {
|
||||
inuptelem.setAttribute('data-type', 'img');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// select 事件
|
||||
form.on('select(select-type)', function (data) {
|
||||
const value = data.value; // 获得被选中的值
|
||||
@@ -111,6 +137,40 @@
|
||||
})()
|
||||
return false;
|
||||
});
|
||||
// 上传二维码图片
|
||||
async function uploadCodeImg(elem) {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = 'image/*';
|
||||
input.onchange = async (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
const formData = new FormData();
|
||||
formData.append('codeimg', file);
|
||||
try {
|
||||
const response = await fetch('/api/PayManage/uploadQrcode', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
if (result.code === 0) {
|
||||
layer.msg(result.msg, { icon: 1, time: 1200 });
|
||||
elem.value = result.data.imgpath;
|
||||
return result.data.imgpath;
|
||||
} else {
|
||||
layer.msg('上传失败:' + result.message, { tips: 2, time: 1200 });
|
||||
}
|
||||
} else {
|
||||
layer.msg('上传失败:服务器错误', { tips: 2, time: 1200 });
|
||||
}
|
||||
} catch (error) {
|
||||
layer.msg('上传失败:' + error.message, { tips: 2, time: 1200 });
|
||||
}
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">收款样式</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="type">
|
||||
<select name="type" lay-filter="type">
|
||||
<option value="">请选择</option>
|
||||
<option value="0">付款链接</option>
|
||||
<option value="1">图片地址</option>
|
||||
@@ -49,10 +49,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">付款地址</label>
|
||||
<label class="layui-form-label">收款码</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="qrcode" autocomplete="off" lay-affix="upload-drag"
|
||||
lay-filter="scanning" class="layui-input">
|
||||
lay-filter="scanning" class="layui-input" data-type="<?php echo match ((int)$type) {0 => 'link',1 => 'img',default => ''}; ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item" pane>
|
||||
@@ -98,8 +98,33 @@
|
||||
"type": '<?php echo $type ?>'
|
||||
});
|
||||
|
||||
form.on('input-affix(scanning)', function () {
|
||||
window.open('https://cli.im/deqr', '_blank');
|
||||
form.on('input-affix(scanning)', function (obj) {
|
||||
const type = obj.elem.getAttribute('data-type');
|
||||
if (type === '') {
|
||||
layer.msg('请先选择二维码类型!', { tips: 2, time: 1200 });
|
||||
return false;
|
||||
}
|
||||
if (type === 'img') {
|
||||
uploadCodeImg(obj.elem);
|
||||
return false;
|
||||
}
|
||||
if (type === 'link') {
|
||||
window.open('https://cli.im/deqr', '_blank');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
form.on('select(type)', function (data) {
|
||||
const form = data.elem.form;
|
||||
const value = data.value;
|
||||
const inuptelem = form.querySelector('input[name="qrcode"]');
|
||||
if (value === '0') {
|
||||
inuptelem.setAttribute('data-type', 'link');
|
||||
return false;
|
||||
}
|
||||
if (value === '1') {
|
||||
inuptelem.setAttribute('data-type', 'img');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
form.on('submit(save)', function (obj) {
|
||||
@@ -126,6 +151,40 @@
|
||||
})()
|
||||
return false;
|
||||
});
|
||||
// 上传二维码图片
|
||||
async function uploadCodeImg(elem) {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = 'image/*';
|
||||
input.onchange = async (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
const formData = new FormData();
|
||||
formData.append('codeimg', file);
|
||||
try {
|
||||
const response = await fetch('/api/PayManage/uploadQrcode', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
if (result.code === 0) {
|
||||
layer.msg(result.msg, { icon: 1, time: 1200 });
|
||||
elem.value = result.data.imgpath;
|
||||
return result.data.imgpath;
|
||||
} else {
|
||||
layer.msg('上传失败:' + result.message, { tips: 2, time: 1200 });
|
||||
}
|
||||
} else {
|
||||
layer.msg('上传失败:服务器错误', { tips: 2, time: 1200 });
|
||||
}
|
||||
} catch (error) {
|
||||
layer.msg('上传失败:' + error.message, { tips: 2, time: 1200 });
|
||||
}
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<script>
|
||||
|
||||