mirror of
https://gitee.com/technical-laohu/mpay.git
synced 2025-11-09 04:03:43 +08:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55fd7f9335 | ||
|
|
36b6cdba8c | ||
|
|
45d26dc524 | ||
|
|
fb025542f2 | ||
|
|
513b0f521d | ||
|
|
27867c65c5 | ||
|
|
6b3f6c5448 | ||
|
|
6c3106a6aa | ||
|
|
8f1d6ce013 |
2
.env
2
.env
@@ -1,4 +1,4 @@
|
||||
APP_DEBUG = false
|
||||
APP_DEBUG = true
|
||||
|
||||
DB_TYPE = mysql
|
||||
DB_HOST = 127.0.0.1
|
||||
|
||||
23
README.md
23
README.md
@@ -38,20 +38,30 @@
|
||||
优点是审核不严,处理比较灵活,适合个人,技术支持比较好,注册可使用(有些需要注册费)
|
||||
缺点就是平台容易卷款跑路,也没有什么有效监管,钱收不回来就亏大了(这也是最大的问题)
|
||||
|
||||
### 源支付
|
||||
### 源支付/V免签
|
||||
|
||||
源支付也是一套收款程序,有个人版和商户版,市面上能搜到的大部分都是商户版,可以入驻,使用自己的个人微信支付宝二维码收款。
|
||||
源支付程序的设计思路主要是通过在手机或电脑上安装消息监听软件,用来监听获取微信和支付宝的收款到账通知来实现的支付成功回调的。**方法很实用,本程序也添加了该功能插件,免费**。
|
||||
|
||||
V免签是一款开源免费适用于个人收款使用的收款程序,原理同源支付类似。
|
||||
程序的设计思路主要是通过在手机或电脑上安装消息监听软件,用来监听获取微信和支付宝的收款到账通知来实现的支付成功回调的。**方法很实用,本程序也添加了该功能插件,免费**。
|
||||
|
||||

|
||||
|
||||
只是这种思路,有一些小问题:
|
||||
|
||||
* 平台容易因为资质问题导致关站;
|
||||
* 收取的手续费价格偏高;
|
||||
* 个人码在微信H5环境无法长按识别付款,只能通过PC端,相机扫码付款。
|
||||
* 挂机监听容易掉线,导致收款通知无法回调
|
||||
|
||||
### 🚀️ 码支付(mpay)
|
||||
|
||||
**本程序暂只提供个人版,开源免费使用。**
|
||||
码支付是在源支付的设计思路基础上进行的改进,利用第四方**聚合收款码**来进行收款,保证收款稳定和便捷。聚合收款码个人可以申请,不需求相关资质,不用申请API接口,收银服务平台众多且实力雄厚(如拉卡拉、收钱吧等),不怕跑路。
|
||||
|
||||
码支付是在源支付的设计思路基础上进行的改进,利用第四方**聚合收款码**来进行收款,保证收款稳定和便捷不掉线。
|
||||
|
||||
聚合收款码个人可以申请,不需求相关资质,不用申请API接口,收银服务平台众多且实力雄厚(如拉卡拉、收钱吧等),不怕跑路。
|
||||
|
||||
特点如下:
|
||||
|
||||
* 免监听,不需要手机或电脑挂机监听消息,即可实现支付回调,只需要设置一个定时任务就行
|
||||
@@ -85,6 +95,8 @@
|
||||
|
||||
当用户付款成功,并且后台检测到收款成功消息后,收钱台就会提示收款成功,并最终确认收款。
|
||||
|
||||

|
||||
|
||||
> 只有存在新订单时,且该订单与当前收款账号一致时,码支付后台才会主动登陆该账号,查询收款流水,减少频繁查询导致的可能风险
|
||||
|
||||
> 另外,在账号设置里也有两个模式可选,`单次监听`和`连续监听`,根据业务场景可以自行选择,具体使用,下面有介绍
|
||||
@@ -223,7 +235,7 @@
|
||||
|
||||
**终端编号**需要填写当前收款码在收银服务商系统内的编码,有的可以直接在收款二维码解析的**链接里找到**,有的需要**登陆商户管理中心**,去订单详情里查询才能知道
|
||||
|
||||
🚀️ 具体各个平台的终端编号如何获取,可以去**程序后端控制台主页**的`项目文档`查看🚀️
|
||||
🚀️ 具体各个平台的终端编号如何获取,可以去**程序后端控制台主页**的`项目文档`查看🚀️
|
||||
|
||||

|
||||
|
||||
@@ -355,6 +367,7 @@
|
||||
|
||||
添加应用转发规则,**微信**和**支付宝**需要分别设置
|
||||
|
||||
|
||||

|
||||
|
||||
**具体设置**
|
||||
@@ -363,6 +376,8 @@
|
||||
2. **匹配字段**选择**多重匹配**,**匹配的值**去**用户中心**复制,然后粘贴过来
|
||||
3. 开启**启用自定义模版**,内容填写去码支付后台**账号列表**里复制,粘贴过来
|
||||
|
||||
**注意:** 微信支付规则里,第三行的`[空格]`需要替换成真实的` `空格
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
@@ -101,14 +101,16 @@ class Order extends BaseModel
|
||||
public static function showOrderDetail($id)
|
||||
{
|
||||
$order = self::find($id);
|
||||
$a_list = PayAccount::with('payChannel')->hasWhere('payChannel', ['id' => $order->cid])->where('PayAccount.id', $order->aid)->find();
|
||||
$a_list = PayAccount::find($order->aid);
|
||||
$c_list = PayChannel::find($order->cid);
|
||||
if (!$order) {
|
||||
return [];
|
||||
}
|
||||
$order->platform = $a_list->platform ?? '···';
|
||||
$order->account = $a_list->account ?? '···';
|
||||
$order->channel = $a_list->payChannel[0]->channel ?? '···';
|
||||
$order->qrcode = $a_list->payChannel[0]->qrcode ?? '···';
|
||||
$order->platform = $a_list['platform'] ?? '···';
|
||||
$order->account = $a_list['account'] ?? '···';
|
||||
$order->channel = $c_list['channel'] ?? '···';
|
||||
$order->qrcode = $c_list['qrcode'] ?? '···';
|
||||
$order->url_type = $c_list['type'] ?? '···';
|
||||
return $order->toArray();
|
||||
}
|
||||
// 选择收款通道
|
||||
|
||||
BIN
assets/20241210_112301_6f2fef2a7aaee96790eb86f90e3b107.png
Normal file
BIN
assets/20241210_112301_6f2fef2a7aaee96790eb86f90e3b107.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 155 KiB |
BIN
assets/20241210_112331_e8d2c4043a3c57ad887aef92df1c253.png
Normal file
BIN
assets/20241210_112331_e8d2c4043a3c57ad887aef92df1c253.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
Binary file not shown.
BIN
public/files/qrcode/img1733715359.jpg
Normal file
BIN
public/files/qrcode/img1733715359.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
@@ -1,58 +1,30 @@
|
||||
// 插件配置
|
||||
const plugins = [
|
||||
// 登陆配置
|
||||
const logins = [
|
||||
{
|
||||
name: '拉卡拉',
|
||||
host: 'm2.lakala.com',
|
||||
method: 'POST',
|
||||
orderQuery: '/m/lamsmerdash/account/pwdLogin',
|
||||
accPath: 'account',
|
||||
pswPath: 'pwd',
|
||||
},
|
||||
{
|
||||
name: '收钱吧',
|
||||
host: 'web-platforms-msp.shouqianba.com',
|
||||
method: 'POST',
|
||||
orderQuery: '/api/transaction/findTransactions',
|
||||
channelKey: 'terminal_device_fingerprint',
|
||||
moneyKey: 'original_amount',
|
||||
listPath: 'data.records'
|
||||
orderQuery: '/api/login/ucUser/login',
|
||||
accPath: 'username',
|
||||
pswPath: 'password',
|
||||
}
|
||||
];
|
||||
|
||||
// 登陆配置
|
||||
const logins = [
|
||||
{
|
||||
name: '新商城',
|
||||
host: 'localhost',
|
||||
method: 'GET',
|
||||
orderQuery: '/api/Order/getOrders',
|
||||
accPath: 'order_id',
|
||||
pswPath: 'money',
|
||||
}
|
||||
];
|
||||
|
||||
// 提取订单信息
|
||||
function extractOrderInfo(response, plugins) {
|
||||
plugins.forEach((plugin) => {
|
||||
const urlObj = new URL(response.url);
|
||||
if (plugin.host === urlObj.hostname
|
||||
&& plugin.orderQuery === urlObj.pathname
|
||||
&& plugin.method === response.method) {
|
||||
const jsonDatas = JSON.parse(response.response);
|
||||
const orderDatas = eval(`jsonDatas.${plugin.listPath}`);
|
||||
let lists = [];
|
||||
orderDatas.forEach((orderData) => {
|
||||
const data = {
|
||||
'终端编号': orderData[plugin.channelKey],
|
||||
'订单金额': orderData[plugin.moneyKey]
|
||||
};
|
||||
lists.push(data);
|
||||
})
|
||||
console.log(plugin.name);
|
||||
console.table(lists);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 提取登陆信息
|
||||
function extractLoginInfo(request, logins) {
|
||||
logins.forEach((login) => {
|
||||
const urlObj = new URL(request.url);
|
||||
if (login.host === urlObj.hostname
|
||||
&& login.orderQuery === urlObj.pathname
|
||||
&& login.method === request.method) {
|
||||
const urlObj = isHttp(request.url, login.host);
|
||||
if (login.host.toLowerCase() === urlObj.hostname.toLowerCase() &&
|
||||
login.orderQuery.toLowerCase() === urlObj.pathname.toLowerCase() &&
|
||||
login.method.toLowerCase() === request.method.toLowerCase()) {
|
||||
const jsonData = JSON.parse(request.request);
|
||||
const acc = eval(`jsonData.${login.accPath}`);
|
||||
const psw = eval(`jsonData.${login.pswPath}`);
|
||||
@@ -60,53 +32,47 @@ function extractLoginInfo(request, logins) {
|
||||
'账号': acc,
|
||||
'密码': psw
|
||||
};
|
||||
console.log(login.name);
|
||||
console.log('----- ' + login.name + ' -----');
|
||||
console.table(data);
|
||||
alert('账号:' + acc + '\n密码:' + psw);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// XHR 重写
|
||||
function isHttp(url, host) {
|
||||
if (url.startsWith('http') || url.startsWith('https')) {
|
||||
return new URL(url);
|
||||
} else {
|
||||
url = 'https://' + host + url;
|
||||
return new URL(url);
|
||||
}
|
||||
}
|
||||
var oldOpen = XMLHttpRequest.prototype.open;
|
||||
var oldSend = XMLHttpRequest.prototype.send;
|
||||
XMLHttpRequest.prototype.open = function (method, url) {
|
||||
this._url = url;
|
||||
this._method = method;
|
||||
const res = {
|
||||
url: this.responseURL,
|
||||
method: this._method,
|
||||
response: this._body
|
||||
}
|
||||
extractLoginInfo(res, logins);
|
||||
return oldOpen.apply(this, arguments);
|
||||
};
|
||||
XMLHttpRequest.prototype.send = function (body) {
|
||||
this._body = body;
|
||||
this.addEventListener('load', function () {
|
||||
const res = {
|
||||
url: this.responseURL,
|
||||
method: this._method,
|
||||
response: this.responseText
|
||||
}
|
||||
console.log(res);
|
||||
|
||||
extractOrderInfo(res, plugins);
|
||||
});
|
||||
const res = {
|
||||
url: this._url,
|
||||
method: this._method,
|
||||
request: this._body
|
||||
};
|
||||
extractLoginInfo(res, logins);
|
||||
return oldSend.apply(this, arguments);
|
||||
};
|
||||
|
||||
// fetch 重写
|
||||
window.au_fetch = window.fetch;
|
||||
window.fetch = function (url, options) {
|
||||
// console.log('Fetch URL:', url);
|
||||
// console.log('Fetch Options:', options);
|
||||
const res = {
|
||||
url: url,
|
||||
method: options.method,
|
||||
request: options.body
|
||||
};
|
||||
extractLoginInfo(res, logins);
|
||||
return window.au_fetch.apply(window, [url, options]).then((response) => {
|
||||
const res = {
|
||||
url: url,
|
||||
method: options.method,
|
||||
response: response.text()
|
||||
}
|
||||
extractOrderInfo(res, plugins);
|
||||
return response;
|
||||
});
|
||||
})
|
||||
};
|
||||
@@ -483,7 +483,7 @@
|
||||
// 点击事件
|
||||
util.on({
|
||||
'load': () => {
|
||||
util.openWin({ url: 'https://gitee.com/technical-laohu/mpay' });
|
||||
util.openWin({ url: 'https://gitee.com/technical-laohu/mpay/releases' });
|
||||
},
|
||||
'doc': () => {
|
||||
util.openWin({ url: 'https://f0bmwzqjtq2.feishu.cn/docx/HBVrdrsACo36bzxUCSPcjOBNnyb?from=from_copylink' });
|
||||
|
||||
@@ -198,7 +198,8 @@
|
||||
<div class="layui-col-xs9">
|
||||
<div class="list">
|
||||
<a href="javascript:;" class="layui-font-blue" lay-on="getQrcode"
|
||||
data-qrcode="<?php echo $qrcode ?>"><span class="icon pear-icon"></span></a>
|
||||
data-qrcode="<?php echo $qrcode ?>" data-type="<?php echo $url_type ?>"><span
|
||||
class="icon pear-icon"></span></a>
|
||||
<?php echo $qrcode ?>
|
||||
</div>
|
||||
</div>
|
||||
@@ -215,16 +216,28 @@
|
||||
util.on({
|
||||
getQrcode: function () {
|
||||
(async () => {
|
||||
const qrcode_data = this.getAttribute("data-qrcode")
|
||||
const qrcode_img = await getQrcode(qrcode_data, QR);
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: ['200px', '200px'],
|
||||
title: false,
|
||||
closeBtn: 0,
|
||||
shadeClose: true,
|
||||
content: `<img width="100%" src="${qrcode_img}">`
|
||||
});
|
||||
const type_data = this.getAttribute("data-type");
|
||||
const qrcode_data = this.getAttribute("data-qrcode");
|
||||
if (type_data == 1) {
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: ['200px', '200px'],
|
||||
title: false,
|
||||
closeBtn: 0,
|
||||
shadeClose: true,
|
||||
content: `<img width="100%" src="${qrcode_data}">`
|
||||
});
|
||||
} else if (type_data == 0) {
|
||||
const qrcode_img = await getQrcode(qrcode_data, QR);
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: ['200px', '200px'],
|
||||
title: false,
|
||||
closeBtn: 0,
|
||||
shadeClose: true,
|
||||
content: `<img width="100%" src="${qrcode_img}">`
|
||||
});
|
||||
}
|
||||
})()
|
||||
}
|
||||
});
|
||||
|
||||
@@ -46,6 +46,11 @@
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div class="button-container">
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<!-- <?php } ?> -->
|
||||
<!-- <?php if ($platform == 'alipay') { ?> -->
|
||||
<option value="alipay1">收钱码</option>
|
||||
<option value="alipay2">经营码</option>
|
||||
<!-- <option value="alipay2">经营码</option> -->
|
||||
<!-- <?php } ?> -->
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -40,16 +40,28 @@
|
||||
util.on({
|
||||
getQrcode: function () {
|
||||
(async () => {
|
||||
const type_data = this.getAttribute("data-type");
|
||||
const qrcode_data = this.getAttribute("data-qrcode")
|
||||
const qrcode_img = await getQrcode(qrcode_data, QR);
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: ['200px', '200px'],
|
||||
title: false,
|
||||
closeBtn: 0,
|
||||
shadeClose: true,
|
||||
content: `<img width="100%" src="${qrcode_img}">`
|
||||
});
|
||||
if (type_data == 1) {
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: ['200px', '200px'],
|
||||
title: false,
|
||||
closeBtn: 0,
|
||||
shadeClose: true,
|
||||
content: `<img width="100%" src="${qrcode_data}">`
|
||||
});
|
||||
} else if (type_data == 0) {
|
||||
const qrcode_img = await getQrcode(qrcode_data, QR);
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: ['200px', '200px'],
|
||||
title: false,
|
||||
closeBtn: 0,
|
||||
shadeClose: true,
|
||||
content: `<img width="100%" src="${qrcode_img}">`
|
||||
});
|
||||
}
|
||||
})()
|
||||
},
|
||||
edit: function () {
|
||||
@@ -104,7 +116,7 @@
|
||||
<div class="layui-card-body">
|
||||
<span class="layui-badge layui-bg-cyan">终端编号</span><span class="layui-badge layui-bg-gray">${item.channel}</span><br>
|
||||
<span class="layui-badge layui-bg-cyan">收款地址</span><span class="layui-badge layui-bg-gray">${qrcode}</span>
|
||||
<a href="javascript:;" class="layui-font-blue" lay-on="getQrcode"data-qrcode="${item.qrcode}"><span class="icon pear-icon"></span></a><br>
|
||||
<a href="javascript:;" class="layui-font-blue" lay-on="getQrcode" data-qrcode="${item.qrcode}" data-type="${item.type}"><span class="icon pear-icon"></span></a><br>
|
||||
<span class="layui-badge layui-bg-cyan">最后使用</span><span class="layui-badge layui-bg-gray">${item.last_time}</span><br>
|
||||
<span class="layui-badge layui-bg-cyan">启用状态</span><span class="layui-badge layui-bg-${item.state == 1 ? 'green' : 'gray'}">${item.state == 1 ? '启用' : '禁用'}</span><br>
|
||||
<div class="layui-btn-group edit">
|
||||
|
||||
@@ -96,6 +96,13 @@
|
||||
}
|
||||
}}
|
||||
</script>
|
||||
<script type="text/html" id="account-trade">
|
||||
{{# if (d.platform == '微信支付' || d.platform == '支付宝') { }}
|
||||
<span class="layui-badge layui-bg-gray">无</span>
|
||||
{{# } else { }}
|
||||
<a href="javascript:;" lay-event="queryTrade" data-url="/api/PayManage/getAccountTrade?pid={{= d.pid }}&aid={{= d.id }}"><span class="layui-badge layui-bg-green">查询</span></a>
|
||||
{{# } }}
|
||||
</script>
|
||||
<script src="/component/layui/layui.js"></script>
|
||||
<script src="/component/pear/pear.js"></script>
|
||||
<script>
|
||||
@@ -124,7 +131,7 @@
|
||||
{ title: '启用状态', field: 'state', align: 'center', templet: '#account-state' },
|
||||
{ title: '监听模式', field: 'pattern', align: 'center' },
|
||||
{ title: '监听地址 / 自定义模版', field: 'checkUrl', align: 'center', minWidth: 300, event: 'copy', templet: '#account-checkUrl' },
|
||||
{ title: '收款平台流水', field: 'trade', align: 'center', templet: '<div><a href="javascript:;" lay-event="queryTrade" data-url="/api/PayManage/getAccountTrade?pid={{= d.pid }}&aid={{= d.id }}"><span class="layui-badge layui-bg-green">查询</span></a></div>' },
|
||||
{ title: '收款平台流水', field: 'trade', align: 'center', templet: '#account-trade' },
|
||||
{ title: '收款码数量', field: 'channel', align: 'center', templet: '<div><a href="javascript:;" lay-event="channelList"><span class="layui-badge layui-bg-green">{{= d.channel }}</span></a></div>' },
|
||||
{ title: '操作', align: 'center', fixed: 'right', templet: '<div><a href="javascript:;" class="layui-font-green" lay-event="edit"><strong>编辑</strong></a></div>' }
|
||||
]]
|
||||
|
||||
@@ -90,11 +90,11 @@
|
||||
// 表头工具事件
|
||||
table.on('toolbar(plugin-table)', function (obj) {
|
||||
if (obj.event === 'showAllPlugin') {
|
||||
table.reload('plugin-table', { where: { show: 0 }, done: () => { plugin.changClass(obj.event) } });
|
||||
table.reloadData('plugin-table', { where: { show: 0 }, done: () => { plugin.changClass(obj.event) } });
|
||||
} else if (obj.event === 'showInstalled') {
|
||||
table.reload('plugin-table', { where: { show: 1 }, done: () => { plugin.changClass(obj.event) } });
|
||||
table.reloadData('plugin-table', { where: { show: 1 }, done: () => { plugin.changClass(obj.event) } });
|
||||
} else if (obj.event === 'showWaitInstall') {
|
||||
table.reload('plugin-table', { where: { show: 2 }, done: () => { plugin.changClass(obj.event) } });
|
||||
table.reloadData('plugin-table', { where: { show: 2 }, done: () => { plugin.changClass(obj.event) } });
|
||||
}
|
||||
});
|
||||
// 单元格事件
|
||||
|
||||
@@ -181,9 +181,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.tencent.mm<br />并且 是 通知标题 相等 微信支付<br /> 或者 是 通知标题 相等 微信收款助手' ?>
|
||||
<?php echo '并且 是 APP包名 相等 com.tencent.mm<br />并且 是 通知标题 相等 微信支付<br />[空格]或者 是 通知标题 相等 微信收款助手' ?>
|
||||
<a href="javascript:;" lay-on="copyinfo"
|
||||
data-info='<?php echo "并且 是 APP包名 相等 com.tencent.mm\n并且 是 通知标题 相等 微信支付\n 或者 是 通知标题 相等 微信收款助手" ?>'
|
||||
data-info='<?php echo "并且 是 APP包名 相等 com.tencent.mm\n并且 是 通知标题 相等 微信支付\n[空格]或者 是 通知标题 相等 微信收款助手" ?>'
|
||||
style="float: right;" title="复制"><span
|
||||
class="icon pear-icon pear-icon-survey"></span></a>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user