mirror of
https://gitee.com/technical-laohu/mpay.git
synced 2025-11-08 03:33:43 +08:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f03d3de109 | ||
|
|
b546977011 | ||
|
|
9080543df4 | ||
|
|
a4b8381395 | ||
|
|
020398b34f | ||
|
|
a7ca3d2db5 | ||
|
|
5d42d2ef70 | ||
|
|
9fbedc8cfc | ||
|
|
b56156d33a | ||
|
|
f9c804c1e5 | ||
|
|
2515515182 | ||
|
|
87563d01d7 | ||
|
|
16f4438a16 | ||
|
|
29c894faae | ||
|
|
a5d09d0a26 | ||
|
|
504aa6c284 | ||
|
|
e65c0e9173 | ||
|
|
2c1ece3eb2 | ||
|
|
ebcac8a9aa | ||
|
|
4eef7f0797 | ||
|
|
dfb8edff22 | ||
|
|
05c1b08e95 | ||
|
|
de848ab32b |
8
.env
8
.env
@@ -1,12 +1,12 @@
|
||||
APP_DEBUG = true
|
||||
APP_DEBUG = false
|
||||
|
||||
DB_TYPE = mysql
|
||||
DB_HOST = 127.0.0.1
|
||||
DB_NAME = mpay
|
||||
DB_USER = admin
|
||||
DB_PASS = Aa123456
|
||||
DB_USER = mpay
|
||||
DB_PASS = 123456
|
||||
DB_PORT = 3306
|
||||
DB_CHARSET = utf8
|
||||
DB_CHARSET = utf8mb4
|
||||
DB_PREFIX = mpay_
|
||||
|
||||
DEFAULT_LANG = zh-cn
|
||||
14
.gitignore
vendored
14
.gitignore
vendored
@@ -1,6 +1,12 @@
|
||||
/.idea
|
||||
/.vscode
|
||||
/.history
|
||||
/.gitee
|
||||
.vscode
|
||||
*.env
|
||||
|
||||
extend/*
|
||||
!extend/ImgCaptcha.php
|
||||
!extend/payclient.zip
|
||||
!extend/Plugin.php
|
||||
|
||||
vendor/*
|
||||
!vendor/vendor.zip
|
||||
|
||||
runtime/*
|
||||
17
README.md
17
README.md
@@ -436,3 +436,20 @@ V免签是一款开源免费适用于个人收款使用的收款程序,原理
|
||||
|
||||

|
||||
|
||||
# 学习交流社群
|
||||
|
||||
社群答疑、插件定制、技术交流,添加微信拉群,请备注:**码支付**
|
||||
|
||||
微信:**K103516**
|
||||
|
||||
|
||||
|
||||
<img src="assets/wxqrcode.png" width=50% />
|
||||
|
||||
|
||||
## 感谢赞助
|
||||
|姓名 | 金额 |
|
||||
|---|---|
|
||||
| 知汇学社 | 100 |
|
||||
| exrock | 100 |
|
||||
| A筱磊 | 6.66 |
|
||||
@@ -22,7 +22,6 @@ class IndexController
|
||||
}
|
||||
public function test()
|
||||
{
|
||||
|
||||
return app()->getBasePath();
|
||||
return View::fetch();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,11 +23,15 @@ class InstallController
|
||||
{
|
||||
// 检查是否已经安装过
|
||||
if ($this->checkLock()) {
|
||||
return backMsg(1, '已经安装');
|
||||
return json(backMsg(1, '已经安装'));
|
||||
};
|
||||
// 检查环境
|
||||
$envCheck = $this->checkEnvironment();
|
||||
if ($envCheck !== true) {
|
||||
return json(backMsg(1, $envCheck));
|
||||
};
|
||||
// 获取表单提交的数据库配置信息
|
||||
$dbConfig = $request->post();
|
||||
|
||||
// 保存数据库配置信息到配置文件
|
||||
if ($this->saveDbConfig($dbConfig) === false) {
|
||||
return json(backMsg(1, '配置保存失败'));
|
||||
@@ -62,7 +66,22 @@ class InstallController
|
||||
$this->setLock();
|
||||
return json(backMsg(0, '安装成功'));
|
||||
}
|
||||
|
||||
private function checkEnvironment()
|
||||
{
|
||||
// 检查PHP版本
|
||||
if (version_compare(PHP_VERSION, '8.0', '<')) {
|
||||
return 'PHP版本必须大于等于8.0';
|
||||
}
|
||||
// 检查文件上传写入权限
|
||||
if (!is_writable(sys_get_temp_dir())) {
|
||||
return '文件上传目录没有写入权限';
|
||||
}
|
||||
// 检查Fileinfo扩展是否安装
|
||||
if (!extension_loaded('fileinfo')) {
|
||||
return 'Fileinfo扩展未安装';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private function saveDbConfig($dbConfig)
|
||||
{
|
||||
$envPath = app()->getRootPath() . '.env';
|
||||
|
||||
@@ -98,6 +98,7 @@ class PayController
|
||||
$notify['sign'] = $sign;
|
||||
// 跳转通知URL
|
||||
$res_return_url = $act_order->return_url . '?' . http_build_query($notify);
|
||||
if (strpos($act_order->return_url, '?')) $res_return_url = $act_order->return_url . '&' . http_build_query($notify);
|
||||
// 响应消息
|
||||
$data['order_id'] = $act_order->order_id;
|
||||
$data['passtime'] = $passtime > 0 ? $passtime : 0;
|
||||
@@ -161,7 +162,7 @@ class PayController
|
||||
// 支付渠道核对
|
||||
$is_channel = $cids[$order->cid] == $new_order['channel'];
|
||||
// 金额核对
|
||||
$is_money = $order->money == $new_order['price'];
|
||||
$is_money = $order->really_price == $new_order['price'];
|
||||
// 订单核对
|
||||
if ($is_payway && $is_channel && $is_money) {
|
||||
$res = $this->updateOrderState($order, $new_order['order_no']);
|
||||
@@ -187,7 +188,9 @@ class PayController
|
||||
$sign = self::getSign($notify, $user_key);
|
||||
$notify['sign'] = $sign;
|
||||
// 异步通知
|
||||
$res_notify = self::getHttpResponse($order->notify_url . '?' . http_build_query($notify));
|
||||
$notify_url = $order->notify_url . '?' . http_build_query($notify);
|
||||
if (strpos($order->notify_url, '?')) $notify_url = $order->notify_url . '&' . http_build_query($notify);
|
||||
$res_notify = self::getHttpResponse($notify_url);
|
||||
if ($res_notify === 'success') {
|
||||
return ['order' => $order->order_id, 'code' => 1, 'msg' => 'notify success'];
|
||||
} else {
|
||||
@@ -201,9 +204,8 @@ class PayController
|
||||
$req_pid = $req_info['pid'];
|
||||
$req_aid = $req_info['aid'];
|
||||
// 获取订单
|
||||
$order_path = runtime_path() . '/order.json';
|
||||
if (!file_exists($order_path)) return json(['code' => 3, 'msg' => '订单文件不存在']);
|
||||
$new_order = json_decode(file_get_contents($order_path), true);
|
||||
$new_order = cache('order');
|
||||
if (!$new_order) return json(['code' => 3, 'msg' => '没有找到新订单缓存']);
|
||||
// 检测新订单
|
||||
if ($new_order['code'] !== 1) return json($new_order);
|
||||
// 订单列表
|
||||
@@ -237,38 +239,31 @@ class PayController
|
||||
return json(['code' => 0, 'msg' => '查询空订单'], 320);
|
||||
}
|
||||
}
|
||||
// [定时任务]监听新订单,生成JSON文件信息
|
||||
// [定时任务]监听新订单,生成缓存
|
||||
public function checkOrder($pid = '', $sign = '')
|
||||
{
|
||||
if (!($pid && $sign)) {
|
||||
return '参数错误';
|
||||
}
|
||||
if (!($pid && $sign)) return '参数错误';
|
||||
$is_user = User::checkUser($pid, $sign);
|
||||
$path = runtime_path() . 'order.json';
|
||||
if ($is_user) {
|
||||
$orders = Order::scope('activeOrder')->field('id,pid,aid,cid,patt')->select();
|
||||
if (!file_exists($path)) {
|
||||
file_put_contents($path, '[]');
|
||||
}
|
||||
$old_info = file_get_contents($path);
|
||||
$old_info = cache('order');
|
||||
$num = count($orders);
|
||||
if ($num > 0) {
|
||||
$info = ['code' => 1, 'msg' => "有{$num}个新订单"];
|
||||
$order_list = ['code' => 1, 'msg' => "有{$num}个新订单", 'orders' => $orders];
|
||||
if ($old_info !== json_encode($order_list)) {
|
||||
file_put_contents($path, json_encode($order_list));
|
||||
if ($old_info !== $order_list) {
|
||||
cache('order', $order_list);
|
||||
}
|
||||
return json($info);
|
||||
} else {
|
||||
$info = ['code' => 0, 'msg' => '没有新订单'];
|
||||
if ($old_info !== json_encode($info, 320)) {
|
||||
file_put_contents($path, json_encode($info, 320));
|
||||
if ($old_info !== $info) {
|
||||
cache('order', $info);
|
||||
}
|
||||
return json($info);
|
||||
}
|
||||
} else {
|
||||
$info = ['code' => 2, 'msg' => '签名错误'];
|
||||
file_put_contents($path, json_encode($info, 320));
|
||||
return json($info);
|
||||
}
|
||||
}
|
||||
@@ -277,18 +272,18 @@ class PayController
|
||||
{
|
||||
$info = $request->post();
|
||||
$action = isset($info['action']) ? $info['action'] : '';
|
||||
if ($action === 'mpay') {
|
||||
$data = json_decode($info['data'], true);
|
||||
$config = PayAccount::getAccountConfig($data['aid'], $data['pid']);
|
||||
$payclient_path = "\\payclient\\{$config['payclass']}";
|
||||
$Payclient = new $payclient_path($info, $config);
|
||||
$res = $Payclient->notify();
|
||||
if (is_int($res)) return $res;
|
||||
$this->payHeart($res, $config);
|
||||
return 200;
|
||||
} else {
|
||||
return 202;
|
||||
}
|
||||
if ($action !== 'mpay' && $action !== 'mpaypc') return '非mpay的访问请求';
|
||||
$data = json_decode($info['data'], true);
|
||||
if (!is_array($data)) return '通知数据为空';
|
||||
if (!isset($data['aid']) || !isset($data['pid'])) return 'aid和pid参数错误';
|
||||
$config = PayAccount::getAccountConfig($data['aid'], $data['pid']);
|
||||
$payclient_path = "\\payclient\\{$config['payclass']}";
|
||||
$Payclient = new $payclient_path($info, $config);
|
||||
if ($action == 'mpay') $res = $Payclient->notify();
|
||||
if ($action == 'mpaypc') $res = $Payclient->pcNotify();
|
||||
if ($res['code'] !== 0) return $res['msg'];
|
||||
$this->payHeart($res['data'], $config);
|
||||
return 200;
|
||||
}
|
||||
// 签名
|
||||
private static function getSign(array $param = [], string $key = ''): string
|
||||
@@ -320,7 +315,8 @@ class PayController
|
||||
'sign_type' => 'MD5',
|
||||
];
|
||||
// 添加扩展参数
|
||||
$notify = array_merge($notify, unserialize($param->param));
|
||||
// $notify = array_merge($notify, unserialize($param->param));
|
||||
$notify['param'] = unserialize($param->param);
|
||||
return $notify;
|
||||
}
|
||||
// 请求外部资源
|
||||
|
||||
@@ -48,7 +48,9 @@ class OrderController extends BaseController
|
||||
$sign = self::getSign($notify, $user_key);
|
||||
$notify['sign'] = $sign;
|
||||
// 异步通知
|
||||
$res_notify = self::getHttpResponse($order->notify_url . '?' . http_build_query($notify));
|
||||
$notify_url = $order->notify_url . '?' . http_build_query($notify);
|
||||
if (strpos($order->notify_url, '?')) $notify_url = $order->notify_url . '&' . http_build_query($notify);
|
||||
$res_notify = self::getHttpResponse($notify_url);
|
||||
if ($res_notify === 'success') {
|
||||
return json(\backMsg(0, '订单通知成功'));
|
||||
} else {
|
||||
@@ -72,7 +74,9 @@ class OrderController extends BaseController
|
||||
$sign = self::getSign($notify, $user_key);
|
||||
$notify['sign'] = $sign;
|
||||
// 异步通知
|
||||
$res_notify = self::getHttpResponse($order->notify_url . '?' . http_build_query($notify));
|
||||
$notify_url = $order->notify_url . '?' . http_build_query($notify);
|
||||
if (strpos($order->notify_url, '?')) $notify_url = $order->notify_url . '&' . http_build_query($notify);
|
||||
$res_notify = self::getHttpResponse($notify_url);
|
||||
if ($res_notify === 'success') {
|
||||
return json(\backMsg(0, '订单通知成功'));
|
||||
} else {
|
||||
@@ -156,7 +160,8 @@ class OrderController extends BaseController
|
||||
'sign_type' => 'MD5',
|
||||
];
|
||||
// 添加扩展参数
|
||||
$notify = array_merge($notify, unserialize($param->param));
|
||||
// $notify = array_merge($notify, unserialize($param->param));
|
||||
$notify['param'] = unserialize($param->param);
|
||||
return $notify;
|
||||
}
|
||||
// 请求外部资源
|
||||
|
||||
@@ -125,14 +125,27 @@ class PayManageController extends BaseController
|
||||
public function uploadQrcode()
|
||||
{
|
||||
$img = $this->request->file('codeimg');
|
||||
if (!$img) {
|
||||
return json(backMsg(1, '请选择要上传的文件'));
|
||||
}
|
||||
// 验证文件类型
|
||||
$allowedTypes = ['image/png', 'image/jpeg', 'image/gif'];
|
||||
$fileMimeType = $img->getMime();
|
||||
if (!in_array($fileMimeType, $allowedTypes)) {
|
||||
return json(backMsg(1, '只允许上传PNG、JPEG或GIF格式的图片'));
|
||||
}
|
||||
// 生成唯一文件名
|
||||
$filename = 'img_' . time() . '_' . uniqid() . '.' . $img->getOriginalExtension();
|
||||
// 设置文件保存路径
|
||||
$path = public_path() . '/files/qrcode/';
|
||||
if (!is_dir($path)) {
|
||||
mkdir($path, 0777, true);
|
||||
mkdir($path, 0755, true);
|
||||
}
|
||||
$info = $img->move($path, 'img' . time() . '.' . $img->getOriginalExtension());
|
||||
// 移动文件到指定目录
|
||||
$info = $img->move($path, $filename);
|
||||
if ($info) {
|
||||
$imgpath = '/files/qrcode/';
|
||||
return json(backMsg(0, '上传成功', ['imgpath' => $imgpath . $info->getFilename()]));
|
||||
$imgpath = '/files/qrcode/' . $filename;
|
||||
return json(backMsg(0, '上传成功', ['imgpath' => $imgpath]));
|
||||
} else {
|
||||
return json(backMsg(1, '上传失败'));
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ class Order extends BaseModel
|
||||
// 设备类型
|
||||
'device' => isset($data['device']) ? $data['device'] : '',
|
||||
// 业务扩展参数
|
||||
'param' => serialize(self::getParams($data)),
|
||||
'param' => serialize(isset($data['param']) ? $data['param'] : ''),
|
||||
// 等待/过期:0, 支付成功:1
|
||||
'state' => 0,
|
||||
// 开启监听:1, 关闭监听:0
|
||||
@@ -148,17 +148,17 @@ class Order extends BaseModel
|
||||
return $channel;
|
||||
}
|
||||
// 获取扩展参数数组
|
||||
private static function getParams(array $data): array
|
||||
{
|
||||
$keys = ['pid', 'type', 'out_trade_no', 'notify_url', 'return_url', 'name', 'money', 'sign', 'sign_type'];
|
||||
$params = [];
|
||||
foreach ($data as $key => $value) {
|
||||
if (!in_array($key, $keys)) {
|
||||
$params[$key] = $value;
|
||||
}
|
||||
}
|
||||
return $params;
|
||||
}
|
||||
// private static function getParams(array $data): array
|
||||
// {
|
||||
// $keys = ['pid', 'type', 'out_trade_no', 'notify_url', 'return_url', 'name', 'money', 'sign', 'sign_type'];
|
||||
// $params = [];
|
||||
// foreach ($data as $key => $value) {
|
||||
// if (!in_array($key, $keys)) {
|
||||
// $params[$key] = $value;
|
||||
// }
|
||||
// }
|
||||
// return $params;
|
||||
// }
|
||||
// 检查金额
|
||||
private static function checkMoney($money, $type, $aid, $cid): float
|
||||
{
|
||||
|
||||
BIN
assets/wxqrcode.png
Normal file
BIN
assets/wxqrcode.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 330 KiB |
1
extend/.gitignore
vendored
1
extend/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
payclient
|
||||
@@ -49,13 +49,23 @@ class Plugin
|
||||
// 获取平台所有支持插件
|
||||
public static function getAllPlugin(): array
|
||||
{
|
||||
$app_plugin = cache('app_plugin');
|
||||
if ($app_plugin) {
|
||||
return json_decode($app_plugin, true);
|
||||
}
|
||||
$app_plugin = self::getHttpResponse(self::$siteUrl . '/mpay/getplugins');
|
||||
cache('app_plugin', $app_plugin, 36000);
|
||||
return json_decode($app_plugin, true);
|
||||
}
|
||||
// 获取通知消息
|
||||
public static function getNotifyMessage(): array
|
||||
{
|
||||
$message = cache('message');
|
||||
if ($message) {
|
||||
return json_decode($message, true);
|
||||
}
|
||||
$message = self::getHttpResponse(self::$siteUrl . '/mpay/message');
|
||||
cache('message', $message, 36000);
|
||||
return json_decode($message, true);
|
||||
}
|
||||
// 请求外部资源
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
2
runtime/.gitignore
vendored
2
runtime/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
*
|
||||
!.gitignore
|
||||
3
vendor/.gitignore
vendored
3
vendor/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
*
|
||||
!.gitignore
|
||||
!vendor.zip
|
||||
@@ -150,6 +150,35 @@
|
||||
</div>
|
||||
<script src="/component/layui/layui.js"></script>
|
||||
<script src="/static/js/awesome-qr.min.js"></script>
|
||||
<script>
|
||||
function detectBrowserEnvironment() {
|
||||
const userAgent = navigator.userAgent.toLowerCase();
|
||||
let environment = 'other';
|
||||
if (userAgent.includes('micromessenger')) {
|
||||
if (userAgent.includes('miniprogram')) {
|
||||
environment = 'wxapp'; // 微信小程序
|
||||
} else if (userAgent.includes('android') || userAgent.includes('iphone') || userAgent.includes('ipad')) {
|
||||
environment = 'wxphone'; // 手机微信
|
||||
} else {
|
||||
environment = 'wxpc'; // PC微信
|
||||
}
|
||||
} else if (userAgent.includes('aliapp') || userAgent.includes('alipayclient')) {
|
||||
if (userAgent.includes('android') || userAgent.includes('iphone') || userAgent.includes('ipad')) {
|
||||
environment = 'aliphone'; // 手机支付宝
|
||||
} else {
|
||||
environment = 'alipc'; // PC支付宝
|
||||
}
|
||||
} else if (userAgent.includes('android') || userAgent.includes('iphone') || userAgent.includes('ipad')) {
|
||||
// 先判断是否是已知的手机APP内置浏览器,如果不是则认为是手机浏览器
|
||||
if (!userAgent.includes('micromessenger') && !userAgent.includes('aliapp') && !userAgent.includes('qq')) {
|
||||
environment = 'phone'; // 手机浏览器
|
||||
}
|
||||
} else {
|
||||
environment = 'pc'; // 剩下的情况认为是PC浏览器
|
||||
}
|
||||
return environment;
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
const payCode = '<?php echo htmlentities($payUrl); ?>';
|
||||
const codeType = '<?php echo htmlentities($code_type); ?>';
|
||||
@@ -247,6 +276,14 @@
|
||||
return info;
|
||||
}
|
||||
/* <?php } ?> */
|
||||
|
||||
// 环境判断
|
||||
const environment = detectBrowserEnvironment();
|
||||
if (payType === 'wxpay' && environment === 'aliphone') {
|
||||
layer.alert('请使用微信打开此页面');
|
||||
} else if (payType === 'alipay' && environment === 'wxphone') {
|
||||
layer.alert('请使用支付宝打开此页面');
|
||||
}
|
||||
// 生成二维码
|
||||
async function getQrcode(text, QR) {
|
||||
const qrcodeUrl = await new Promise((resolve) => {
|
||||
|
||||
@@ -39,10 +39,10 @@
|
||||
lay-filter="scanning" class="layui-input" data-type="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="main-container">
|
||||
<div class="layui-text">
|
||||
<p>终端编号如何填写,<strong><a href="https://f0bmwzqjtq2.feishu.cn/docx/HBVrdrsACo36bzxUCSPcjOBNnyb?from=from_copylink" target="_blank"><strong>请查看文档</strong></a></p>
|
||||
<div class="layui-form-item">
|
||||
<blockquote class="layui-elem-quote">
|
||||
<p>首次添加,不知道终端编号可以先空着,后面再查询填写。查询终端编号,<strong><a class="layui-font-blue" href="https://f0bmwzqjtq2.feishu.cn/docx/HBVrdrsACo36bzxUCSPcjOBNnyb?from=from_copylink" target="_blank"><strong>请查看文档</strong></a></p>
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -30,12 +30,12 @@
|
||||
<!-- <?php if ($platform == 'wxpay') { ?> -->
|
||||
<option value="wxpay1">个人码</option>
|
||||
<option value="wxpay2">赞赏码</option>
|
||||
<!-- <option value="wxpay3">经营码</option> -->
|
||||
<option value="wxpay3">经营码</option>
|
||||
<option value="wxpay4">商家码</option>
|
||||
<!-- <?php } ?> -->
|
||||
<!-- <?php if ($platform == 'alipay') { ?> -->
|
||||
<option value="alipay1">收钱码</option>
|
||||
<!-- <option value="alipay2">经营码</option> -->
|
||||
<option value="alipay2">经营码</option>
|
||||
<!-- <?php } ?> -->
|
||||
</select>
|
||||
</div>
|
||||
@@ -57,6 +57,18 @@
|
||||
lay-filter="scanning" class="layui-input" data-type="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<blockquote class="layui-elem-quote">
|
||||
<!-- <?php if ($platform == 'wxpay') { ?> -->
|
||||
<p><strong>手机监控:</strong>微信个人码与经营码只能二选一,不能同时添加,商家码监听需要关注“微信收款商业版”公众号,赞赏码正常添加。</p>
|
||||
<p><strong>电脑监控:</strong>所有收款码均可监控,将需要将收款通知聊天窗口单独拖出来</p>
|
||||
<!-- <?php } ?> -->
|
||||
<!-- <?php if ($platform == 'alipay') { ?> -->
|
||||
<p><strong>手机监控:</strong>支付宝收钱码与经营码只能二选一</p>
|
||||
<p><strong>电脑监控:</strong>暂不支持PC端监控,可以选择支付宝账单方式监听收款</p>
|
||||
<!-- <?php } ?> -->
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
|
||||
Reference in New Issue
Block a user