21 Commits

Author SHA1 Message Date
技术老胡
898daee64c 文件调整 2024-12-14 09:22:00 +08:00
技术老胡
b266e1ad7c 更新文件结构 2024-12-12 17:14:01 +08:00
技术老胡
e85e71f8d9 更新后台管理接口 2024-12-12 16:45:57 +08:00
技术老胡
6a8ec71b8d 收款通知逻辑更新,完全改为本地接口,避免网络提交带来的延迟 2024-12-12 16:34:39 +08:00
技术老胡
7f77c5a1dd 更新文档 2024-12-11 17:28:31 +08:00
技术老胡
d61ec241f1 更新文件 2024-12-11 16:34:58 +08:00
技术老胡
4aa9feb5be 更新文件 2024-12-11 16:34:32 +08:00
技术老胡
16d4b989d8 更新文件 2024-12-11 16:30:11 +08:00
技术老胡
8e5981f640 更新文件 2024-12-11 16:27:27 +08:00
技术老胡
8a52346425 修改支付测试逻辑,核对支付方式 2024-12-11 16:23:21 +08:00
技术老胡
21db31e8a0 更新文件 2024-12-11 15:37:35 +08:00
技术老胡
b5d128d6b4 修复获取器空键名报错 2024-12-11 15:28:32 +08:00
技术老胡
55fd7f9335 修复微信监听配置不回调问题 2024-12-11 11:20:58 +08:00
技术老胡
36b6cdba8c 更新结构 2024-12-10 13:57:24 +08:00
技术老胡
45d26dc524 文档更新 2024-12-10 11:24:30 +08:00
技术老胡
fb025542f2 UI交互优化 2024-12-09 15:59:09 +08:00
技术老胡
513b0f521d 二维码生成优化 2024-12-09 15:31:19 +08:00
技术老胡
27867c65c5 优化二维码生成交互逻辑 2024-12-09 15:23:17 +08:00
技术老胡
6b3f6c5448 一些UI交互处理 2024-12-09 14:40:21 +08:00
技术老胡
6c3106a6aa 文档更新 2024-12-07 17:16:17 +08:00
技术老胡
8f1d6ce013 文档更新 2024-12-07 11:50:03 +08:00
35 changed files with 237 additions and 526 deletions

12
!!.env
View File

@@ -1,12 +0,0 @@
APP_DEBUG = true
DB_TYPE = mysql
DB_HOST = 127.0.0.1
DB_NAME = test
DB_USER = test
DB_PASS = 123456
DB_PORT = 3306
DB_CHARSET = utf8
DB_PREFIX = mpay_
DEFAULT_LANG = zh-cn

2
.env
View File

@@ -1,4 +1,4 @@
APP_DEBUG = false
APP_DEBUG = true
DB_TYPE = mysql
DB_HOST = 127.0.0.1

View File

@@ -38,20 +38,30 @@
优点是审核不严,处理比较灵活,适合个人,技术支持比较好,注册可使用(有些需要注册费)
缺点就是平台容易卷款跑路,也没有什么有效监管,钱收不回来就亏大了(这也是最大的问题)
### 源支付
### 源支付/V免签
源支付也是一套收款程序,有个人版和商户版,市面上能搜到的大部分都是商户版,可以入驻,使用自己的个人微信支付宝二维码收款。
源支付程序的设计思路主要是通过在手机或电脑上安装消息监听软件,用来监听获取微信和支付宝的收款到账通知来实现的支付成功回调的。**方法很实用,本程序也添加了该功能插件,免费**。
V免签是一款开源免费适用于个人收款使用的收款程序原理同源支付类似。
程序的设计思路主要是通过在手机或电脑上安装消息监听软件,用来监听获取微信和支付宝的收款到账通知来实现的支付成功回调的。**方法很实用,本程序也添加了该功能插件,免费**。
![](assets/20241210_112331_e8d2c4043a3c57ad887aef92df1c253.png)
只是这种思路,有一些小问题:
* 平台容易因为资质问题导致关站;
* 收取的手续费价格偏高;
* 个人码在微信H5环境无法长按识别付款只能通过PC端相机扫码付款。
* 挂机监听容易掉线,导致收款通知无法回调
### 🚀️ 码支付(mpay)
**本程序暂只提供个人版,开源免费使用。**
码支付是在源支付的设计思路基础上进行的改进,利用第四方**聚合收款码**来进行收款保证收款稳定和便捷。聚合收款码个人可以申请不需求相关资质不用申请API接口收银服务平台众多且实力雄厚如拉卡拉、收钱吧等不怕跑路。
码支付是在源支付的设计思路基础上进行的改进,利用第四方**聚合收款码**来进行收款,保证收款稳定和便捷不掉线。
聚合收款码个人可以申请不需求相关资质不用申请API接口收银服务平台众多且实力雄厚如拉卡拉、收钱吧等不怕跑路。
特点如下:
* 免监听,不需要手机或电脑挂机监听消息,即可实现支付回调,只需要设置一个定时任务就行
@@ -85,12 +95,18 @@
当用户付款成功,并且后台检测到收款成功消息后,收钱台就会提示收款成功,并最终确认收款。
![](assets/20241210_112301_6f2fef2a7aaee96790eb86f90e3b107.png)
> 只有存在新订单时,且该订单与当前收款账号一致时,码支付后台才会主动登陆该账号,查询收款流水,减少频繁查询导致的可能风险
> 另外,在账号设置里也有两个模式可选,`单次监听`和`连续监听`,根据业务场景可以自行选择,具体使用,下面有介绍
# 开源声明
## 演示站点
[码支付](http://demo.stspwsc.com/) http://demo.stspwsc.com/
## 内置插件
码支付收款管理系统开源免费使用,支付插件有**免费**和**付费**版本,按需使用购买
@@ -223,7 +239,7 @@
**终端编号**需要填写当前收款码在收银服务商系统内的编码,有的可以直接在收款二维码解析的**链接里找到**,有的需要**登陆商户管理中心**,去订单详情里查询才能知道
🚀️ 具体各个平台的终端编号如何获取,可以去**程序后端控制台主页**的`项目文档`查看🚀️
🚀️ 具体各个平台的终端编号如何获取,可以去**程序后端控制台主页**的`项目文档`查看🚀️
![](assets/20241205_110508_image.png)
@@ -363,6 +379,8 @@
2. **匹配字段**选择**多重匹配****匹配的值**去**用户中心**复制,然后粘贴过来
3. 开启**启用自定义模版**,内容填写去码支付后台**账号列表**里复制,粘贴过来
**注意:** 微信支付规则里,第三行的`[空格]`需要替换成真实的` `空格
![](assets/20241204_172718_c313070e899cc93cc2fc9fe25a1ff17.jpg)
![](assets/20241204_175634_image.png)

View File

@@ -1 +0,0 @@
deny from all

View File

@@ -13,14 +13,14 @@ class ConsoleController extends BaseController
// 后台主页
public function index()
{
View::assign('version', 'v1.0.1');
View::assign('version', 'v1');
return View::fetch();
}
// 管理菜单
public function menu()
{
// 加载菜单配置
$menu = \think\facade\Config::load("extendconfig/menu", 'extendconfig');
$menu = \think\facade\Config::load("extend/menu", 'extend');
return json($menu);
}
// 管理菜单

View File

@@ -6,6 +6,7 @@ use think\Request;
use think\facade\View;
use app\model\User;
use app\model\Order;
use app\model\PayAccount;
use app\model\PayChannel;
class PayController
@@ -157,31 +158,20 @@ class PayController
}
}
// 处理收款通知
public function payHeart(Request $request)
private function payHeart(array $records, array $config)
{
$pid = $request->get('pid');
$aid = $request->get('aid');
$sign = $request->get('sign');
// 检测请求参数
if (!($pid && $aid && $sign)) {
return json(['code' => 0, 'msg' => '参数错误']);
}
$pid = $config['pid'];
$aid = $config['aid'];
// 检测收款通知
$payList = $request->post();
if (!$payList) {
if (!$records) {
return json(['code' => 0, 'msg' => '空收款通知']);
}
// 签名验证
$is_user = User::checkUser($pid, $sign);
if (!$is_user) {
return json(['code' => 0, 'msg' => '签名错误']);
}
// 当前用户账号
$query = ['pid' => $pid, 'aid' => $aid];
// 排除已支付订单
$doneOrders = Order::scope('dealOrder')->where($query)->column('platform_order');
$new_orders = [];
foreach ($payList as $order) {
foreach ($records as $order) {
if (!in_array($order['order_no'], $doneOrders)) $new_orders[] = $order;
}
if (!count($new_orders)) return json(['code' => 0, 'msg' => '收款通知无新消息']);
@@ -233,66 +223,47 @@ class PayController
return ['order' => $order->order_id, 'code' => 0, 'msg' => 'notify fail'];
}
}
// [定时任务]获取收款明细,提交收款通知[本地版]
// [定时任务]获取收款明细,提交收款通知
public function checkPayResult(Request $request)
{
$req_info = $request->get();
$req_pid = $req_info['pid'];
$req_aid = $req_info['aid'];
// 加载配置文件
$config = \think\facade\Config::load("payconfig/{$req_pid}_{$req_aid}", 'payconfig');
// 用户账号配置
$user_config = isset($config['user']) ? $config['user'] : [];
// 收款平台账号配置
$pay_config = isset($config['pay']) ? $config['pay'] : [];
// 配置检查
if ($user_config && $pay_config) {
// 账号配置信息
$pid = $user_config['pid'];
$aid = $pay_config['aid'];
if (!($req_pid == $pid && $req_aid == $aid)) {
return '监听收款配置不一致';
}
} else {
return '监听收款配置文件名错误';
}
// 当前站点
$user_config['host'] = \request()->domain();
// 实例化支付类
$Mpay = new \MpayClass($user_config);
// 获取订单
$res_new_order = $Mpay->orderHeart();
$new_order = json_decode($res_new_order, true);
$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);
// 检测新订单
if ($new_order['code'] !== 1) return $res_new_order;
if ($new_order['code'] !== 1) return json($new_order);
// 订单列表
$order_list = $new_order['orders'];
// 检测本账号订单
$orders = [];
foreach ($order_list as $key => $val) {
if ($pid == $val['pid'] && $aid == $val['aid'] && $val['patt'] == 1) {
if ($req_pid == $val['pid'] && $req_aid == $val['aid'] && $val['patt'] == 1) {
$orders[] = $order_list[$key];
}
}
if (!$orders) {
return \json(['code' => 0, 'msg' => '非本账号订单或监听模式不对']);
}
if (!$orders) return json(['code' => 0, 'msg' => '非本账号订单或监听模式不对']);
// 加载配置文件
$config = PayAccount::getAccountConfig($req_aid);
if ($config === false) return json(['code' => 4, 'msg' => '监听收款配置错误']);
// 登陆账号
$config = ['username' => $pay_config['account'], 'password' => $pay_config['password']];
$pay_config = ['username' => $config['account'], 'password' => $config['password']];
// 收款查询
$query = $pay_config['query'];
$query = $config['query'];
// 实例监听客户端
$payclient_name = $pay_config['payclass'];
$payclient_name = $config['payclass'];
$payclient_path = "\\payclient\\{$payclient_name}";
$Payclient = new $payclient_path($config);
$Payclient = new $payclient_path($pay_config);
// 获取支付明细
$records = $Payclient->getOrderInfo($query);
if ($records) {
// 提交收款记录
$upres = $Mpay->upRecords($records, $aid);
$upres = $this->payHeart($records, $config);
return $upres;
} else {
return \json(['code' => 0, 'msg' => '查询空订单'], 320);
return json(['code' => 0, 'msg' => '查询空订单'], 320);
}
}
// [定时任务]监听新订单,生成JSON文件信息
@@ -337,10 +308,11 @@ class PayController
$action = isset($info['action']) ? $info['action'] : '';
if ($action === 'mpay') {
$data = json_decode($info['data'], true);
$config = \think\facade\Config::load("payconfig/{$data['pid']}_{$data['aid']}", 'payconfig');
$payclient_path = "\\payclient\\{$config['pay']['payclass']}";
$Payclient = new $payclient_path($info, $config, $request->domain());
$Payclient->notify();
$config = PayAccount::getAccountConfig($data['aid'], $data['pid']);
$payclient_path = "\\payclient\\{$config['payclass']}";
$Payclient = new $payclient_path($info, $config);
$res = $Payclient->notify();
$this->payHeart($res, $config);
return 200;
} else {
return 202;

View File

@@ -1,85 +0,0 @@
<?php
declare (strict_types = 1);
namespace app\controller;
use think\Request;
class SystemController
{
/**
* 显示资源列表
*
* @return \think\Response
*/
public function index()
{
return '系统设置';
}
/**
* 显示创建资源表单页.
*
* @return \think\Response
*/
public function create()
{
//
}
/**
* 保存新建的资源
*
* @param \think\Request $request
* @return \think\Response
*/
public function save(Request $request)
{
//
}
/**
* 显示指定的资源
*
* @param int $id
* @return \think\Response
*/
public function read($id)
{
//
}
/**
* 显示编辑资源表单页.
*
* @param int $id
* @return \think\Response
*/
public function edit($id)
{
//
}
/**
* 保存更新的资源
*
* @param \think\Request $request
* @param int $id
* @return \think\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* 删除指定资源
*
* @param int $id
* @return \think\Response
*/
public function delete($id)
{
//
}
}

View File

@@ -7,7 +7,6 @@ namespace app\controller\api;
use app\BaseController;
use app\model\PayAccount;
use app\model\PayChannel;
use app\model\User;
class PayManageController extends BaseController
{
@@ -28,9 +27,9 @@ class PayManageController extends BaseController
$aid = $this->request->post('aid');
$res = PayChannel::where(['account_id' => $aid])->order('last_time', 'desc')->select();
if ($res) {
return \json(\backMsg(0, '获取成功', $res));
return json(backMsg(0, '获取成功', $res));
} else {
return \json(\backMsg(1, '失败'));
return json(backMsg(1, '失败'));
}
}
// 账号状态
@@ -39,9 +38,9 @@ class PayManageController extends BaseController
$info = $this->request->post();
$up_res = PayAccount::update($info);
if ($up_res) {
return json(\backMsg(0, '成功'));
return json(backMsg(0, '成功'));
} else {
return json(\backMsg(1, '失败'));
return json(backMsg(1, '失败'));
}
}
// 添加账号
@@ -53,17 +52,13 @@ class PayManageController extends BaseController
$info['params'] = '{}';
$check_acc = PayAccount::where(['account' => $info['account'], 'platform' => $info['platform'], 'pid' => $pid])->find();
if ($check_acc) {
return \json(\backMsg(1, '账号已存在'));
return json(backMsg(1, '账号已存在'));
}
$acc = PayAccount::create($info);
if ($acc) {
$state = $this->createAccountConfig($acc);
if (!$state) {
return json(\backMsg(1, '自字义参数错误'));
}
return \json(\backMsg(0, '添加成功'));
return json(backMsg(0, '添加成功'));
} else {
return \json(\backMsg(1, '添加失败'));
return json(backMsg(1, '添加失败'));
}
}
// 编辑账号
@@ -72,14 +67,9 @@ class PayManageController extends BaseController
$info = $this->request->post();
$up_res = PayAccount::update($info);
if ($up_res) {
$acc = PayAccount::find($info['id']);
$state = $this->createAccountConfig($acc);
if (!$state) {
return json(\backMsg(1, '自字义参数错误'));
}
return json(\backMsg(0, '修改成功'));
return json(backMsg(0, '修改成功'));
} else {
return json(\backMsg(1, '修改失败'));
return json(backMsg(1, '修改失败'));
}
}
// 删除账号
@@ -89,13 +79,9 @@ class PayManageController extends BaseController
$res = PayAccount::destroy($ids);
$res2 = PayChannel::whereIn('account_id', $ids)->select()->delete();
if ($res && $res2) {
$accs = PayAccount::whereIn('id', $ids)->withTrashed()->select();
foreach ($accs as $acc) {
$this->delAccountConfig($acc);
}
return \json(\backMsg(0, '已删除'));
return json(backMsg(0, '已删除'));
} else {
return \json(\backMsg(1, '失败'));
return json(backMsg(1, '失败'));
}
}
// 添加收款终端
@@ -104,13 +90,13 @@ class PayManageController extends BaseController
$info = $this->request->post();
$check = PayChannel::where(['account_id' => $info['account_id'], 'channel' => $info['channel']])->count();
if ($check) {
return \json(\backMsg(1, '编号已存在'));
return json(backMsg(1, '编号已存在'));
}
$res = PayChannel::create($info);
if ($res) {
return \json(\backMsg(0, '添加成功'));
return json(backMsg(0, '添加成功'));
} else {
return \json(\backMsg(1, '添加失败'));
return json(backMsg(1, '添加失败'));
}
}
// 编辑收款终端
@@ -119,9 +105,9 @@ class PayManageController extends BaseController
$info = $this->request->post();
$up_res = PayChannel::update($info);
if ($up_res) {
return json(\backMsg(0, '修改成功'));
return json(backMsg(0, '修改成功'));
} else {
return json(\backMsg(1, '修改失败'));
return json(backMsg(1, '修改失败'));
}
}
// 删除收款终端
@@ -130,17 +116,9 @@ class PayManageController extends BaseController
$cid = $this->request->post('id');
$res = PayChannel::destroy($cid);
if ($res) {
return \json(\backMsg(0, '已删除'));
return json(backMsg(0, '已删除'));
} else {
return \json(\backMsg(1, '失败'));
}
}
// 删除账号配置
public function delAccountConfig($acc)
{
$path = config_path() . "/payconfig/{$acc->pid}_{$acc->id}.php";
if (file_exists($path)) {
unlink($path);
return json(backMsg(1, '失败'));
}
}
// 上传二维码图片
@@ -166,25 +144,17 @@ class PayManageController extends BaseController
$req_pid = $req_info['pid'];
$req_aid = $req_info['aid'];
// 加载配置文件
$config = \think\facade\Config::load("payconfig/{$req_pid}_{$req_aid}", 'payconfig');
// 收款平台账号配置
$pay_config = isset($config['pay']) ? $config['pay'] : [];
// 配置检查
if ($pay_config) {
// 账号配置信息
$aid = $pay_config['aid'];
if ($req_aid != $aid) return '监听收款配置不一致';
} else {
return '监听收款配置文件名错误';
}
$config = PayAccount::getAccountConfig($req_aid);
if ($config === false) return json(backMsg(1, '账号配置文件错误'));
if ($req_aid != $config['aid'] || $req_pid != session('pid')) return json(backMsg(1, '监听收款配置不一致'));
// 登陆账号
$config = ['username' => $pay_config['account'], 'password' => $pay_config['password']];
$pay_config = ['username' => $config['account'], 'password' => $config['password']];
// 收款查询
$query = $pay_config['query'];
$query = $config['query'];
// 实例监听客户端
$payclient_name = $pay_config['payclass'];
$payclient_name = $config['payclass'];
$payclient_path = "\\payclient\\{$payclient_name}";
$Payclient = new $payclient_path($config);
$Payclient = new $payclient_path($pay_config);
// 获取支付明细
$records = $Payclient->getOrderInfo($query);
if ($records) {
@@ -194,51 +164,4 @@ class PayManageController extends BaseController
return json(backMsg(1, '查询空订单'));
}
}
// 生成账号配置
private function createAccountConfig($acc)
{
$params = \json_decode($acc->params, \true);
if ($params === null) {
return false; // 自定义参数错误
}
$platform = \app\controller\api\PluginController::getPluginInfo($acc->getData('platform'));
$user = User::where('pid', $acc->pid)->find();
$query_tpl = $platform['query'];
$query = var_export(\array_merge($query_tpl, $params), \true);
$config = <<<EOF
<?php
// +----------------------------------------------------------------------
// | 支付监听配置,一个文件,一个账号
// +----------------------------------------------------------------------
return [
// 用户账号配置
'user' => [
'pid' => {$user->pid},
'key' => '{$user->secret_key}'
],
// 收款平台账号配置
'pay' => [
// 账号id
'aid' => $acc->id,
// 收款平台
'platform' => '{$acc->getData('platform')}',
// 插件类名
'payclass' => '{$platform['class_name']}',
// 账号
'account' => '{$acc->account}',
// 密码
'password' => '{$acc->password}',
// 订单查询参数配置
'query' => {$query},
]
];
EOF;
$name = "{$user->pid}_{$acc->id}";
$path = config_path() . "/payconfig/{$name}.php";
\file_put_contents($path, $config);
return true;
}
}

View File

@@ -171,7 +171,7 @@ class PluginController extends BaseController
// 获取插件配置
private static function getPluginConfig(): array
{
$payplugin_path = config_path() . '/extendconfig/payplugin.php';
$payplugin_path = config_path() . '/extend/payplugin.php';
if (!file_exists($payplugin_path)) {
return [];
}
@@ -182,7 +182,7 @@ class PluginController extends BaseController
// 保存插件配置
private function savePluginConfig(array $config, string $note = '说明')
{
$payplugin_path = config_path() . '/extendconfig/payplugin.php';
$payplugin_path = config_path() . '/extend/payplugin.php';
$note_tpl = <<<EOF
// +----------------------------------------------------------------------
// | $note

View File

@@ -17,6 +17,7 @@ class Order extends BaseModel
{
$my_time = time();
$channel = self::setChannel($data['pid'], $data['type']);
if(!$channel) return false;
$new_order = [
// 订单号
'order_id' => self::createOrderID('H'),
@@ -101,14 +102,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();
}
// 选择收款通道
@@ -122,6 +125,7 @@ class Order extends BaseModel
foreach ($channel_infos as $key => $value) {
$check_wx = preg_match('/^wxpay\d+#/i', $value->channel);
$check_ali = preg_match('/^alipay\d+#/i', $value->channel);
$channel_info = null;
if ($check_wx && $type === 'wxpay') {
$channel_info = $channel_infos[$key];
break;
@@ -136,6 +140,7 @@ class Order extends BaseModel
break;
}
}
if(!$channel_info) return [];
// 选取收款通道
$patt = PayAccount::find($channel_info->account_id);
$channel = ['aid' => $channel_info->account_id, 'cid' => $channel_info->id, 'patt' => $patt->getData('pattern')];

View File

@@ -5,6 +5,8 @@ declare(strict_types=1);
namespace app\model;
use app\BaseModel;
use app\model\User;
use app\controller\api\PluginController;
class PayAccount extends BaseModel
{
@@ -24,10 +26,44 @@ class PayAccount extends BaseModel
}
return self::withCount(['payChannel' => 'channel'])->where($select);
}
// 获取账号配置
public static function getAccountConfig($aid, $pid = null): array|bool
{
$aid_info = self::find($aid);
// 插件配置
$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,
// 账号id
'aid' => $aid_info->id,
// 收款平台
'platform' => $aid_info->getData('platform'),
// 插件类名
'payclass' => $platform['class_name'],
// 账号
'account' => $aid_info->account,
// 密码
'password' => $aid_info->password,
// 订单查询参数配置
'query' => $query,
];
if ($pid !== null) {
$pid_info = User::where('pid', $pid)->find();
$config['key'] = $pid_info->secret_key;
}
return $config;
} else {
return false;
}
}
// 获取器
public function getPlatformAttr($value)
{
$payplugin_path = config_path() . '/extendconfig/payplugin.php';
$payplugin_path = config_path() . '/extend/payplugin.php';
if (!file_exists($payplugin_path)) {
return [];
}
@@ -37,7 +73,7 @@ class PayAccount extends BaseModel
foreach ($payplugin_config as $config) {
$option[$config['platform']] = $config['name'];
}
return $option[$value];
return isset($option[$value]) ? $option[$value] : '[已卸载,请停用]';
}
public function getPatternAttr($value)
{

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

1
extend/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/payclient

View File

@@ -23,7 +23,7 @@ class ImgCaptcha
return $captcha;
}
// 请求外部资源
private function getHttpResponse($url, $header = [], $post = null, $timeout = 10)
private static function getHttpResponse($url, $header = [], $post = null, $timeout = 10)
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);

View File

@@ -1,66 +0,0 @@
<?php
declare(strict_types=1);
class MpayClass
{
private $pid;
private $key;
private $host;
private $check_neworder_url;
private $submit_records_url;
function __construct($config)
{
$this->pid = $config['pid'];
$this->key = $config['key'];
$this->host = $config['host'];
$this->check_neworder_url = $this->host . '/order.php';
$this->submit_records_url = $this->host . '/payHeart';
}
// 查询新订单
public function orderHeart()
{
$url = $this->check_neworder_url . "?pid={$this->pid}&sign={$this->getSign()}";
$res = $this->getHttpResponse($url);
return $res;
}
// 提交收款明细
public function upRecords($records, $aid)
{
$header = ['Content-Type: application/json;charset=UTF-8'];
$url = $this->submit_records_url . "?pid={$this->pid}&aid={$aid}&sign={$this->getSign()}";
$res = $this->getHttpResponse($url, $header, json_encode($records));
return $res;
}
// 签名方法
private function getSign()
{
return md5($this->pid . $this->key);
}
// 请求外部资源
private function getHttpResponse($url, $header = [], $post = null, $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.8";
$httpheader[] = "Connection: close";
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader);
}
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
if ($post) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
}
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@@ -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;
});
})
};

2
runtime/.gitignore vendored Normal file
View File

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

View File

@@ -1 +0,0 @@
1733190738

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
{"code":0,"msg":"没有新订单"}

View File

@@ -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' });

View File

@@ -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">&#xe6cc;</span></a>
data-qrcode="<?php echo $qrcode ?>" data-type="<?php echo $url_type ?>"><span
class="icon pear-icon">&#xe6cc;</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}">`
});
}
})()
}
});

View File

@@ -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">

View File

@@ -35,7 +35,7 @@
<!-- <?php } ?> -->
<!-- <?php if ($platform == 'alipay') { ?> -->
<option value="alipay1">收钱码</option>
<option value="alipay2">经营码</option>
<!-- <option value="alipay2">经营码</option> -->
<!-- <?php } ?> -->
</select>
</div>

View File

@@ -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">&#xe6cc;</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">&#xe6cc;</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">

View File

@@ -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>' }
]]

View File

@@ -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) } });
}
});
// 单元格事件

View File

@@ -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 />&nbsp;或者 是 通知标题 相等 微信收款助手' ?>
<?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>