Compare commits

...

7 Commits

Author SHA1 Message Date
技术老胡
872afd10cb 更新文档 2025-09-16 11:07:11 +08:00
技术老胡
924042aa5e 更新支付宝插件 2025-06-28 10:09:45 +08:00
技术老胡
5b69aded08 修复一些交互问题 2025-06-03 11:41:16 +08:00
技术老胡
5826a7f6e1 1、更新通道统计,可以查看每个通道的收入情况,可以查询指定时间段的通道收入情况
2、账号管理列表交互优化,添加操作下拉框
3、修复二维码图片上传功能可能导致的漏洞
2025-06-03 11:15:12 +08:00
技术老胡
98c90dffcf 更新图片上传可能的漏洞 2025-05-09 09:06:48 +08:00
技术老胡
e524424628 更新文件 2025-04-23 23:51:21 +08:00
技术老胡
a6b5133cc3 Update .gitignore rules 2025-04-18 18:10:02 +08:00
20 changed files with 435 additions and 219 deletions

11
.env
View File

@ -1,11 +0,0 @@
APP_DEBUG = false
DB_TYPE = mysql
DB_HOST = 127.0.0.1
DB_NAME = mpay
DB_USER = mpay
DB_PASS = 123456
DB_PORT = 3306
DB_PREFIX = mpay_
DEFAULT_LANG = zh-cn

8
.gitignore vendored
View File

@ -2,14 +2,12 @@
*.env
extend/*
!extend/ImgCaptcha.php
!extend/payclient.zip
!extend/Plugin.php
vendor/*
!vendor/vendor.zip
runtime/*
app/*
!app/controller/TestController.php
app/controller/TestController.php
config/extend/*

View File

@ -39,7 +39,7 @@
## ✨ 演示站点
访问 [在线演示](http://demo.stspwsc.com/) 快速体验。
#### 访问 [在线演示](http://demo.stspwsc.com/) 快速体验。
## 📊 项目说明

BIN
app/app.zip Normal file

Binary file not shown.

View File

@ -68,7 +68,14 @@ class PayController
View::assign('passtime', $passtime > 0 ? $passtime : 0);
// Alipay免输
if (preg_match('/^alipay4#\d+$/', $channel->channel)) {
$payurl = \payclient\AliPayf::getPayUrl($act_order->order_id, $act_order->money, $channel->qrcode);
$chan = request()->get('chan', '');
if ($chan && $chan == 'Alipayf') {
$payurl = \payclient\AliPayf::getPayUrl($act_order->order_id, $act_order->money, $channel->qrcode, 1);
View::assign('payUrl', $payurl['data'] ?? $payurl['msg']);
View::assign('payclient', 'Alipayf');
} else {
$payurl = \payclient\AliPayf::getPayUrl($act_order->order_id, $act_order->money, $channel->qrcode);
}
View::assign('payUrl', $payurl['data'] ?? $payurl['msg']);
} else {
View::assign('payUrl', $channel->qrcode);
@ -237,7 +244,7 @@ class PayController
$config = PayAccount::getAccountConfig($req_aid);
if ($config === false) return json(['code' => 4, 'msg' => '监听收款配置错误']);
// 登陆账号
$pay_config = ['username' => $config['account'], 'password' => $config['password']];
$pay_config = ['username' => $config['account'], 'password' => $config['password'], 'aid' => $config['aid']];
// 配置参数
$params = $config['params'];
// 实例监听客户端

View File

@ -74,4 +74,9 @@ class PayManageController extends BaseController
View::assign(['id' => $id]);
return View::fetch();
}
// 收款统计
public function payStatistics()
{
return View::fetch();
}
}

View File

@ -7,6 +7,9 @@ namespace app\controller\api;
use app\BaseController;
use app\model\PayAccount;
use app\model\PayChannel;
use app\model\Order;
use think\facade\Db;
use \think\facade\Log;
class PayManageController extends BaseController
{
@ -125,30 +128,59 @@ 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, 0755, true);
}
// 移动文件到指定目录
$info = $img->move($path, $filename);
if ($info) {
$imgpath = '/files/qrcode/' . $filename;
return json(backMsg(0, '上传成功', ['imgpath' => $imgpath]));
} else {
return json(backMsg(1, '上传失败'));
try {
// 获取上传的文件
$img = $this->request->file('codeimg');
if (!$img) {
return json(backMsg(1, '请选择要上传的文件'));
}
// 验证文件大小,防止大文件攻击
$maxSize = 2 * 1024 * 1024; // 2MB
if ($img->getSize() > $maxSize) {
return json(backMsg(1, '文件大小不能超过 2MB'));
}
// 验证文件类型,防止恶意文件上传
$allowedTypes = ['image/png', 'image/jpeg', 'image/gif'];
$fileMimeType = $img->getMime();
if (!in_array($fileMimeType, $allowedTypes)) {
return json(backMsg(1, '只允许上传 PNG、JPEG 或 GIF 格式的图片'));
}
// 二次验证文件类型,通过文件内容判断
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$realMimeType = finfo_file($finfo, $img->getRealPath());
finfo_close($finfo);
if (!in_array($realMimeType, $allowedTypes)) {
return json(backMsg(1, '文件类型验证失败,请上传有效的图片文件'));
}
// 生成唯一文件名,避免文件名冲突
$filename = 'img_' . time() . '_' . uniqid() . '.' . $img->getOriginalExtension();
// 过滤文件名,防止路径遍历攻击
$filename = preg_replace('/[^a-zA-Z0-9_\-\.]/', '', $filename);
// 设置文件保存路径
$path = public_path() . '/files/qrcode/';
if (!is_dir($path)) {
if (!mkdir($path, 0755, true)) {
return json(backMsg(1, '创建目录失败'));
}
}
// 移动文件到指定目录
$info = $img->move($path, $filename);
if ($info) {
$imgpath = '/files/qrcode/' . $filename;
return json(backMsg(0, '上传成功', ['imgpath' => $imgpath]));
} else {
return json(backMsg(1, '上传失败'));
}
} catch (\Exception $e) {
Log::error('上传过程中出现异常: ' . $e->getMessage());
return json(backMsg(1, '上传过程中出现异常,请稍后重试'));
}
}
// 获取账号交易流水
@ -162,7 +194,7 @@ class PayManageController extends BaseController
if ($config === false) return json(backMsg(1, '账号配置文件错误'));
if ($req_aid != $config['aid'] || $req_pid != session('pid')) return json(backMsg(1, '监听收款配置不一致'));
// 登陆账号
$pay_config = ['username' => $config['account'], 'password' => $config['password']];
$pay_config = ['username' => $config['account'], 'password' => $config['password'], 'aid' => $config['aid']];
// 收款查询
$params = $config['params'];
// 实例监听客户端
@ -178,4 +210,73 @@ class PayManageController extends BaseController
return json(['code' => 1, 'msg' => $records['msg']]);
}
}
public function payStatisticsList()
{
$query = $this->request->get();
$limit = $query['limit'] ?? 10;
$page = $query['page'] ?? 1;
$start_time = $query['time_start'] ?? date('Y-m-d H:i:s', strtotime('today'));
$end_time = $query['time_end'] ?? date('Y-m-d H:i:s', strtotime('tomorrow') - 1);
// 确保日期时间格式正确
$start_time = date('Y-m-d H:i:s', strtotime($start_time));
$end_time = date('Y-m-d H:i:s', strtotime($end_time));
$accounts = Db::table('mpay_pay_account', 'PayAccount')
->alias('PayAccount')
->join('mpay_order Order', 'PayAccount.id = Order.aid AND Order.delete_time IS NULL AND Order.state = 1', 'LEFT')
->field([
'PayAccount.*',
'SUM(CASE WHEN DATE(Order.pay_time) = CURDATE() THEN Order.really_price ELSE 0 END) as day',
'SUM(CASE WHEN DATE(Order.pay_time) = DATE_SUB(CURDATE(), INTERVAL 1 DAY) THEN Order.really_price ELSE 0 END) as yesterday',
'SUM(CASE WHEN YEARWEEK(Order.pay_time, 1) = YEARWEEK(CURDATE(), 1) THEN Order.really_price ELSE 0 END) as week',
'SUM(CASE WHEN DATE_FORMAT(Order.pay_time, "%Y-%m") = DATE_FORMAT(CURDATE(), "%Y-%m") THEN Order.really_price ELSE 0 END) as month',
'SUM(CASE WHEN YEAR(Order.pay_time) = YEAR(CURDATE()) THEN Order.really_price ELSE 0 END) as year',
'SUM(IFNULL(Order.really_price, 0)) as total',
"SUM(CASE WHEN Order.pay_time BETWEEN '$start_time' AND '$end_time' THEN Order.really_price ELSE 0 END) as income"
])
->where('PayAccount.delete_time IS NULL')
->group('PayAccount.id')
->order('PayAccount.id', 'DESC')
->paginate(['list_rows' => $limit, 'page' => $page]);
return json([
'code' => 0,
'msg' => 'OK',
'count' => $accounts->total(),
'data' => $accounts->items()
]);
}
// 收款统计
// public function payStatisticsList()
// {
// $query = $this->request->get();
// // 定义统计字段
// $fields = [
// "SUM(IF(DATE(pay_time) = CURDATE(), really_price, 0)) as day",
// "SUM(IF(DATE(pay_time) = CURDATE() - INTERVAL 1 DAY, really_price, 0)) as yesterday",
// "SUM(IF(YEARWEEK(pay_time, 1) = YEARWEEK(CURDATE(), 1), really_price, 0)) as week",
// "SUM(IF(DATE_FORMAT(pay_time, '%Y-%m') = DATE_FORMAT(CURDATE(), '%Y-%m'), really_price, 0)) as month",
// "SUM(IF(YEAR(pay_time) = YEAR(CURDATE()), really_price, 0)) as year",
// "SUM(really_price) as total"
// ];
// $where = ['state', 1;
// // 合并 pay_account 表字段和统计字段
// $allFields = array_merge([PayAccount::getTable() . '.*'], $fields);
// $accounts = PayAccount::hasWhere('order', $where, '*', 'LEFT')
// ->field($allFields)
// ->group(PayAccount::getTable() . '.id')
// ->order('id', 'desc')
// ->paginate(['list_rows' => $query['limit'] ?? 10, 'page' => $query['page'] ?? 1]);
// if ($accounts) {
// return json(['code' => 0, 'msg' => PayAccount::getLastSql(), 'count' => $accounts->total(), 'data' => $accounts->items()]);
// } else {
// return json(['code' => 1, 'msg' => '无数据记录', 'count' => 0, 'data' => []]);
// }
// }
}

View File

@ -48,6 +48,8 @@ class Order extends BaseModel
'state' => 0,
// 开启监听1, 关闭监听0
'patt' => $channel['patt'],
// 平台
'platform' => $channel['platform'],
// 订单创建时间
'create_time' => self::getFormatTime($my_time),
// 订单关闭时间
@ -136,7 +138,7 @@ class Order extends BaseModel
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'), 'chan' => $channel_info->channel];
$channel = ['aid' => $channel_info->account_id, 'cid' => $channel_info->id, 'patt' => $patt->getData('pattern'), 'chan' => $channel_info->channel, 'platform' => $patt->getData('platform')];
PayChannel::update(['last_time' => self::getFormatTime(), 'id' => $channel['cid']]);
return backMsg(0, 'ok', $channel);
}
@ -186,7 +188,9 @@ class Order extends BaseModel
// 生成订单号
private static function createOrderID(string $prefix = ''): string
{
return $prefix . date('Ymd') . substr(implode('', array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
$date = date('YmdHis');
$rand = rand(1000, 9999);
return $prefix . $date . $rand;
}
// 查询有效期内的未支付订单
public function scopeActiveOrder($query)
@ -206,6 +210,6 @@ class Order extends BaseModel
// 模型多对一关联
public function payAccount()
{
return $this->belongsTo(PayAccount::class, 'aid', 'id');
return $this->belongsTo(PayAccount::class, 'id', 'aid');
}
}

View File

@ -24,7 +24,25 @@ class PayAccount extends BaseModel
$select[] = [$key, '=', $value];
}
}
return self::withCount(['payChannel' => 'channel'])->where($select);
return self::withCount(['payChannel' => 'channel_num'])->withSum(['order' => function ($query, &$alias) {
$query->whereDay('pay_time')->where('state', 1);
$alias = 'income';
}], 'really_price')->where($select);
}
public static function findAccount($query)
{
$select = [];
$allow_field = ['state', 'platform', 'account', 'pattern'];
foreach ($query as $key => $value) {
if (in_array($key, $allow_field) && isset($value)) {
if ($key === 'account') {
$select[] = [$key, 'like', '%' . $value . '%'];
continue;
}
$select[] = [$key, '=', $value];
}
}
return self::where($select);
}
// 获取账号配置
public static function getAccountConfig($aid, $pid = null): array|bool
@ -86,4 +104,9 @@ class PayAccount extends BaseModel
{
return $this->hasMany(PayChannel::class, 'account_id', 'id');
}
// 一对多关联
public function order()
{
return $this->hasMany(Order::class, 'aid', 'id');
}
}

View File

@ -1,72 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | 后台菜单配置
// +----------------------------------------------------------------------
return [
[
'id' => 'console',
'title' => '平台首页',
'icon' => 'icon pear-icon pear-icon-home',
'type' => 1,
'openType' => '_iframe',
'href' => 'Console/console',
],
[
'id' => 'order',
'title' => '订单管理',
'icon' => 'icon pear-icon pear-icon-survey',
'type' => 1,
'openType' => '_iframe',
'href' => '/Order/index',
],
[
'id' => 'payManage',
'title' => '账号管理',
'icon' => 'icon pear-icon pear-icon-security',
'type' => 1,
'openType' => '_iframe',
'href' => '/PayManage/index',
],
[
'id' => 'pluginManage',
'title' => '插件管理',
'icon' => 'icon pear-icon pear-icon-modular',
'type' => 1,
'openType' => '_iframe',
'href' => '/Plugin/index',
],
[
'id' => 'userCenter',
'title' => '用户中心',
'icon' => 'icon pear-icon pear-icon-user',
'type' => 1,
'openType' => '_iframe',
'href' => '/User/index',
],
// [
// 'id' => 'system',
// 'title' => '系统设置',
// 'icon' => 'icon pear-icon pear-icon-import',
// 'type' => 1,
// 'openType' => '_iframe',
// 'href' => '/System/index',
// ],
// [
// 'id' => 'pay',
// 'title' => '支付管理',
// 'icon' => 'icon pear-icon pear-icon-import',
// 'type' => 0,
// 'href' => '',
// 'children' => [
// [
// 'id' => 'pay_qrcode_list',
// 'title' => '收款账户',
// 'icon' => 'icon pear-icon pear-icon-import',
// 'type' => 1,
// 'openType' => '_iframe',
// 'href' => '/PayQrcode/index',
// ],
// ],
// ],
];

View File

@ -1,43 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | 支付插件列表
// +----------------------------------------------------------------------
return array (
0 =>
array (
'platform' => 'wxpay',
'name' => '微信支付',
'class_name' => 'WxPay',
'price' => NULL,
'describe' => '支持微信个人收款码、赞赏码、经营码、商家码收款,监听回调',
'website' => 'https://weixin.qq.com',
'helplink' => 'https://f0bmwzqjtq2.feishu.cn/docx/JBshdZWokoWzLmxSKQWcSd4Jncg',
'version' => '1.0',
'state' => 1,
),
1 =>
array (
'platform' => 'alipay',
'name' => '支付宝',
'class_name' => 'AliPay',
'price' => NULL,
'describe' => '支持支付宝个人收款码、经营码收款,监听回调',
'website' => 'https://www.alipay.com',
'helplink' => 'https://f0bmwzqjtq2.feishu.cn/docx/GfltdDrjxoYXwexhtX8ckDCBn9f',
'version' => '1.0',
'state' => 1,
),
2 =>
array (
'platform' => 'sqbpay',
'name' => '收钱吧',
'class_name' => 'ShouQianBa',
'price' => NULL,
'describe' => '主流移动支付全能收 信用卡,花呗都能用,生意帮手收钱吧,移动收款就用它!',
'website' => 'https://www.shouqianba.com',
'helplink' => 'https://f0bmwzqjtq2.feishu.cn/docx/WwN7dRqmuoozlIxgiuFclZhCnte',
'version' => '1.0',
'state' => 1,
),
);

View File

@ -4,7 +4,7 @@ declare(strict_types=1);
class Plugin
{
private static $siteUrl = 'https://api.zhaidashi.cn';
private static $siteUrl = 'https://api.qcjy.cc';
// 获取全部插件(含本地)
public static function getAllPlugins(array $local_plugin = []): array
{
@ -64,9 +64,9 @@ class Plugin
{
$message = cache('message');
if ($message) return $message;
$message = self::getHttpResponse(self::$siteUrl . '/MpayApi', ['action' => 'message']);
$message = self::getHttpResponse(self::$siteUrl . '/MpayApi', ['action' => 'message'], [], 3);
$info = json_decode($message, true);
if($info === null) return [];
if ($info === null) return [];
if ($info['code'] === 0) cache('message', $info['data'], 36000);
return $info['data'];
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -113,7 +113,7 @@
</a></li>
<?php }else{ ?>
<li><a href="/User/login" class="a-head-btn bdr-5 bg-color-white">登录</a></li>
<li><a href="/" class="a-head-btn bdr-5 bg-color-0055ff">注册</a></li>
<!-- <li><a href="/" class="a-head-btn bdr-5 bg-color-0055ff">注册</a></li> -->
<?php } ?>
</ul>
</div>
@ -127,7 +127,7 @@
<div class="title-heading mt-4">
<h1 class="heading mb-3">码支付<small class="text-success" style="font-size: 50%">
[ˈheɪloʊ]</small></h1>
<p class="para-desc text-muted">源支付,扫码支付,免签支付,聚合支付,码支付免签约,聚合支付平台</p>
<p class="para-desc text-muted">基于易支付接口开发的免签收款工具,方便个人在线收款</p>
<div class="mt-4 pt-2">
<a href="/User/login" class="btn btn-primary mr-2"><svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img"
@ -322,7 +322,7 @@
document.write(new Date().getFullYear());
</script>2024 <a href="/">码支付</a> - All
rights reserved<span class="sep"> | </span><a href="https://beian.miit.gov.cn/"
target="_blank" rel="noreferrer nofollow">湘ICP备2023031541号-2</a>
target="_blank" rel="noreferrer nofollow">湘ICP备********号</a>
</p>
</div>
</div>

View File

@ -128,15 +128,6 @@
清空过期
</button>
</script>
<script type="text/html" id="platformTpl">
<div>
{{#
let platforms = JSON.parse(sessionStorage.getItem('platforms')) || {};
let platformName = platforms[d.platform] || '已卸载';
return `${platformName} [${d.aid}:${d.cid}]`;
}}
</div>
</script>
<script src="/component/layui/layui.js"></script>
<script src="/component/pear/pear.js"></script>
<script>
@ -223,6 +214,16 @@
form.render('select');
// 会话存储
sessionStorage.setItem('platforms', JSON.stringify(platforms));
let platformTpl = (d) => {
let platforms = JSON.parse(sessionStorage.getItem('platforms')) || {};
let platformName = platforms[d.platform] || '已卸载';
return `${platformName} [${d.aid}:${d.cid}]`;
}
const urlParams = new URLSearchParams(window.location.search);
const where = {};
urlParams.forEach((value, key) => {
where[key] = value;
});
// 表格列参数
let cols = [[
{ type: 'checkbox' },
@ -235,7 +236,7 @@
{ title: '支付状态', field: 'state', align: 'center', minWidth: 85, templet: '<div>{{# if(d.state==1){return`<span class="layui-badge layui-bg-green">成功</span>`}else{if(new Date(d.close_time)>new Date("<?php echo $servertime ?>")){return`<span class="layui-badge layui-bg-orange">等待</span>`}else{return`<span class="layui-badge layui-bg-gray">过期</span>`} } }}</div>' },
{ title: '支付时间', field: 'pay_time', align: 'center', minWidth: 160, templet: '<div>{{= d.pay_time == d.create_time ? "- -" : d.pay_time}}</div>' },
{ title: '支付平台', field: 'type', align: 'center', width: 120, templet: '<div>{{# if(d.type=="wxpay"){return`<div class="paytype"><img src="/static/img/wxpay.ico"width="15"><span>微信支付</span></div>`}if(d.type=="alipay"){return`<div class="paytype"><img src="/static/img/alipay.ico"width="15"><span>支付宝</span></div>`}if(d.type=="unionpay"){return`<div class="paytype"><img src="/static/img/unionpay.ico"width="15"><span>云闪付</span></div>`} }}</div>' },
{ title: '收款平台[账号:终端]', field: 'platform', align: 'center', minWidth: 160, templet: '#platformTpl' },
{ title: '收款平台[账号:终端]', field: 'platform', align: 'center', minWidth: 160, templet: platformTpl },
{ title: '操作', align: 'center', width: 120, fixed: 'right', templet: '<div><strong><a href="javascript:;" data-id="{{= d.id }}" class="layui-font-green {{= d.state==1 ? "orderSet-paid" : "orderSet-paying" }}">设置</a></strong></div>' }
]]
// 表格渲染
@ -243,6 +244,7 @@
id: 'orders-table',
elem: '#orders-table',
url: '/api/Order/getOrders',
where: where,
page: true,
cols: cols,
skin: 'line',

View File

@ -6,6 +6,44 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>收银台</title>
<link rel="stylesheet" href="/component/pear/css/pear.css" />
<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;
}
const payCode = '<?php echo $payUrl; ?>';
const codeType = '<?php echo htmlentities($code_type); ?>';
const payType = '<?php echo htmlentities($type); ?>';
const order = '<?php echo htmlentities($order_id); ?>';
const environment = detectBrowserEnvironment();
const payclient = '<?php echo htmlentities($payclient ?? ""); ?>';
if (payclient == 'Alipayf' && environment == 'aliphone') {
window.location.href = payCode;
}
</script>
<style>
body {
background: #f7f7f7;
@ -157,39 +195,6 @@
<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 $payUrl; ?>';
const codeType = '<?php echo htmlentities($code_type); ?>';
const payType = '<?php echo htmlentities($type); ?>';
const order = '<?php echo htmlentities($order_id); ?>';
const QR = AwesomeQR.AwesomeQR;
(async () => {
// 支付类型
@ -207,7 +212,16 @@
}
// 生成二维码
if (codeType == 0) {
document.getElementById('qrcode').src = await getQrcode(payCode, QR);
if (payType === 'alipay' && environment === 'pc') {
// 解析网址
const url = new URL(payCode);
const queryParams = new URLSearchParams(url.search);
const qrcode = queryParams.get('qrcode');
const decodedQrcode = decodeURIComponent(qrcode);
document.getElementById('qrcode').src = await getQrcode(decodedQrcode, QR);
} else {
document.getElementById('qrcode').src = await getQrcode(payCode, QR);
}
} else {
document.getElementById('qrcode').src = payCode;
}
@ -284,7 +298,6 @@
/* <?php } ?> */
// 环境判断
const environment = detectBrowserEnvironment();
if (payType === 'wxpay' && environment === 'aliphone') {
layer.alert('请使用微信打开此页面');
} else if (payType === 'alipay' && environment === 'wxphone') {

View File

@ -106,10 +106,11 @@
<script src="/component/layui/layui.js"></script>
<script src="/component/pear/pear.js"></script>
<script>
layui.use(['table', 'form', 'common', 'util'], function () {
layui.use(['table', 'form', 'common', 'dropdown', 'util'], function () {
let table = layui.table;
let form = layui.form;
let common = layui.common;
let dropdown = layui.dropdown;
let util = layui.util;
// 渲染插件选项
@ -130,10 +131,11 @@
{ title: '账 号', field: 'account', align: 'center' },
{ 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: 'checkUrl', align: 'center', minWidth: 240, event: 'copy', templet: '#account-checkUrl' },
{ 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>' }
{ title: '收款码数量', field: 'channel_num', align: 'center', templet: '<div><a href="javascript:;" lay-event="channelList"><span class="layui-badge layui-bg-green">{{= d.channel_num }}</span></a></div>' },
{ title: '今日收款', field: 'income', align: 'center', templet: '<div><strong>{{# return d.income ?? 0 }}</strong></div>' },
{ title: '操作', align: 'center', fixed: 'right', templet: '<div><a href="javascript:;" class="layui-font-green edit" data-aid="{{= d.id }}"><strong>编辑</strong></a></div>' }
]]
table.render({
@ -148,7 +150,17 @@
title: '刷新',
layEvent: 'refresh',
icon: 'layui-icon-refresh',
}, 'filter', 'print', 'exports']
}, 'filter', 'print', 'exports'],
done: function () {
dropdown.render({
elem: '.edit',
align: 'center',
data: [{ title: '编辑', id: 1 }, { type: '-' }, { title: '二维码', id: 2 }, { title: '收款统计', id: 3 }, { title: '收款明细', id: 4 }],
click: function (data, othis) {
account.doEdit(data, this.elem);
}
});
}
});
// 事件处理
@ -226,6 +238,27 @@
// 操作方法
let account = {};
// 编辑操作
account.doEdit = function (data, elem) {
const type = data.id;
const id = elem.attr('data-aid');
switch (type) {
case 1:
account.editAccount(id);
break;
case 2:
account.channelList(id);
break;
case 3:
account.tradeList(id);
break;
case 4:
account.orderList(id);
break;
}
}
// 编辑
account.editAccount = function (id) {
layer.open({
@ -237,6 +270,29 @@
content: `/PayManage/editAccount?id=${id}`,
});
}
// 收款统计
account.tradeList = function (aid) {
// 先尝试关闭已存在的标签
if (parent.layui.tab) {
parent.layui.tab.delTabByElem('content', 'payStatistics');
}
// 重新打开标签
parent.layui.tab.addTabOnlyByElem('content',
{ id: 'payStatistics', title: '收款统计', url: `/PayManage/payStatistics`, close: true }
)
}
// 终端列表
account.orderList = function (aid) {
// 先尝试关闭已存在的标签
if (parent.layui.tab) {
parent.layui.tab.delTabByElem('content', 'order');
}
// 重新打开标签
parent.layui.tab.addTabOnlyByElem('content',
{ id: 'order', title: '订单管理', url: `/Order/index?aid=${aid}`, close: true }
)
parent.layui.tab.changeTabTitleById('content', 'order', `【${aid}】订单明细`);
}
// 终端列表
account.channelList = function (id) {
layer.open({

View File

@ -0,0 +1,133 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>收款管理</title>
<link rel="stylesheet" href="/component/pear/css/pear.css" />
<style>
.account-trade {
padding: 15px;
}
</style>
</head>
<body class="pear-container">
<div class="layui-card">
<div class="layui-card-body">
<form class="layui-form layui-form-pane" action="" id="serch-form">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">时间段</label>
<div class="layui-inline" id="create_time">
<div class="layui-input-inline">
<input type="text" name="time_start" autocomplete="off" placeholder="开始"
class="layui-input">
</div>
<div class="layui-form-mid">-</div>
<div class="layui-input-inline">
<input type="text" name="time_end" autocomplete="off" placeholder="结束"
class="layui-input">
</div>
</div>
</div>
<div class="layui-inline">
<button type="submit" class="pear-btn pear-btn-md pear-btn-primary" lay-submit
lay-filter="query">
<i class="layui-icon layui-icon-search"></i>
查询
</button>
<button type="button" lay-on="reset" class="pear-btn pear-btn-md">
<i class="layui-icon layui-icon-refresh"></i>
重置
</button>
</div>
</div>
</form>
</div>
</div>
<div class="layui-card">
<div class="layui-card-body">
<table id="account-table" lay-filter="account-table"></table>
</div>
</div>
<script src="/component/layui/layui.js"></script>
<script src="/component/pear/pear.js"></script>
<script>
layui.use(['table', 'form', 'common', 'dropdown', 'util'], function () {
let table = layui.table;
let form = layui.form;
let common = layui.common;
let dropdown = layui.dropdown;
let util = layui.util;
let laydate = layui.laydate;
const now = new Date();
const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);
const endOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59);
laydate.render({
elem: '#create_time',
range: ['input[name="time_start"]', 'input[name="time_end"]'],
rangeLinked: true,
type: 'datetime',
fullPanel: true,
weekStart: 1,
// 当天开始日期 - 当天结束日期
value: util.toDateString(startOfDay, 'yyyy-MM-dd HH:mm:ss') + ' - ' + util.toDateString(endOfDay, 'yyyy-MM-dd HH:mm:ss')
});
let cols = [[
{ type: 'checkbox' },
{ title: '平 台', field: 'platform', align: 'center', templet: '' },
{ title: '账 号', field: 'account', align: 'center' },
{ title: '时段查询收款', field: 'income', align: 'center', templet: '<div><strong>{{# return d.income ?? 0 }}</strong></div>' },
{ title: '今日收款', field: 'day', align: 'center', templet: '<div><strong>{{# return d.day ?? 0 }}</strong></div>' },
{ title: '昨日收款', field: 'yesterday', align: 'center', templet: '<div><strong>{{# return d.yesterday ?? 0 }}</strong></div>' },
{ title: '本周收款', field: 'week', align: 'center', templet: '<div><strong>{{# return d.week ?? 0 }}</strong></div>' },
{ title: '本月收款', field: 'month', align: 'center', templet: '<div><strong>{{# return d.month ?? 0 }}</strong></div>' },
{ title: '当年收款', field: 'year', align: 'center', templet: '<div><strong>{{# return d.year ?? 0 }}</strong></div>' },
{ title: '总计收款', field: 'total', align: 'center', templet: '<div><strong>{{# return d.total ?? 0 }}</strong></div>' },
]]
table.render({
id: 'account-table',
elem: '#account-table',
url: '/api/PayManage/payStatisticsList',
page: true,
cols: cols,
skin: 'line',
defaultToolbar: [{
title: '刷新',
layEvent: 'refresh',
icon: 'layui-icon-refresh',
}, 'filter', 'print', 'exports'],
});
// 表单自定义事件监听
form.on('submit(query)', function (obj) {
const field = obj.field;
let new_field = {};
for (const key in field) {
if (field.hasOwnProperty.call(field, key)) {
const value = field[key];
if (value) {
new_field[key] = value;
}
}
}
table.reload('account-table', { where: new_field });
return false;
});
// 监听重置按钮
util.on({
reset: function () {
document.querySelector('#serch-form').reset();
table.reload('account-table', { where: {} });
}
});
})
</script>
</body>
</html>

BIN
view/view.zip Normal file

Binary file not shown.