chore: 重写初始提交(清空历史,整理后全量提交)

This commit is contained in:
ywxapp
2026-08-16 16:54:14 +08:00
commit 6c1a106bc1
1808 changed files with 238144 additions and 0 deletions
+332
View File
@@ -0,0 +1,332 @@
<?php
// +----------------------------------------------------------------------
// | YwxApp [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: ywxapp<admin@ywxapp.cn>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace addon\mqpay\controller;
use think\facade\Db;
use think\facade\Event;
use think\facade\Request;
/**
* 收银台 / 异步回调 / 订单查询(示例插件自有控制器,经 MultiApp 自动路由到 /Mqpay/pay/*
*/
class Pay
{
/**
* 真实网关异步通知入口:method 来自查询参数,交由 PaymentNotify 事件验签与激活
*/
public function notify()
{
$method = Request::param('method', '');
$result = Event::trigger('PaymentNotify', [
'method' => $method,
'request' => Request::param(),
'get' => Request::get(),
'post' => Request::post(),
]);
foreach ((array) $result as $r) {
if (is_string($r) && $r !== '') {
return $r;
}
}
return 'fail';
}
/**
* 同步跳转页(支付宝 return_url):展示支付结果,引导返回会员中心
*/
public function cashier()
{
$orderSn = Request::param('out_trade_no', '') ?: Request::param('order_sn', '');
$order = $orderSn ? Db::name('Mqpay_order')->where('order_sn', $orderSn)->find() : [];
$paid = !empty($order) && (int) $order['status'] === 1;
$plan = $order['plan_title'] ?? '';
$title = $paid ? '支付成功' : '支付结果确认中';
$tip = $paid ? '会员已开通,可返回会员中心查看。' : '如已支付,请稍候或返回会员中心查看开通状态。';
$html = <<<HTML
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>支付结果</title>
<link rel="stylesheet" href="/assets/layui/css/layui.css">
</head>
<body style="background:#f2f3f5;">
<div style="max-width:480px;margin:80px auto;background:#fff;padding:30px;border-radius:8px;text-align:center;">
<h2 style="color:#009688;">{$title}</h2>
<p>套餐:{$plan}</p>
<p style="color:#999;">{$tip}</p>
<a href="/user/payment" class="layui-btn">返回会员中心</a>
</div>
</body>
</html>
HTML;
return $html;
}
/**
* 免签支付手动确认到账(站长在管理页点「确认已收款」时调用)
* 请求:POST order_sn=xxx
*/
public function confirm()
{
$orderSn = Request::param('order_sn', '');
// 复用订阅器的确认逻辑(保持单一出口,避免重复实现激活流程)
$subscriber = new \addon\Mqpay\subscribe\Payment();
$result = $subscriber->confirmOrder($orderSn);
return json($result);
}
/**
* 免签支付测试页(用户侧体验 + 联调):选套餐→展示收款码与精确金额→轮询+模拟监听回调
* 路由:/Mqpay/test
*/
public function test()
{
$plans = config('member.plans', []);
$html = $this->renderTestPage(array_values($plans));
return $html;
}
/**
* 安卓监听 App 回调入口(进阶免签自动确认)
* 请求:POST {order_sn?, amount?, sign}
* - order_sn + sign:直接确认指定订单
* - amount + sign:按实际到账金额精确匹配待支付订单(零头防撞单)
* sign = md5(listen_key + amount + order_sn + listen_key)
* 路由:/Mqpay/pay/notify-app
*/
public function notifyApp()
{
$payload = [
'order_sn' => Request::param('order_sn', ''),
'amount' => Request::param('amount', ''),
'sign' => Request::param('sign', ''),
];
$subscriber = new \addon\Mqpay\subscribe\Payment();
$result = $subscriber->notifyApp($payload);
return json($result);
}
/**
* 免签支付管理页:列出待确认订单,一键确认到账
* 路由:/Mqpay/admin
*/
public function admin()
{
$orders = Db::name('Mqpay_order')
->order('id', 'desc')
->limit(50)
->select()
->toArray();
$listenOn = (string) config('mqpay.listen_enable', '0') === '1'
&& (string) config('mqpay.personal_confirm_mode', 'manual') === 'auto';
$html = $this->renderAdminPage($orders, $listenOn);
return $html;
}
/**
* 渲染管理页 HTML(轻量内联,避免引入额外模板引擎依赖)
*/
private function renderAdminPage(array $orders, bool $listenOn = false): string
{
$listenTip = $listenOn
? '<blockquote class="layui-elem-quote layui-quote-nm">已开启「到账自动监听」:安卓监听 App 识别到转账后将自动确认开通,无需手动操作。手动确认仍可用。</blockquote>'
: '';
$rows = '';
foreach ($orders as $o) {
$status = (int) $o['status'] === 1 ? '<span style="color:#009688;">已确认</span>' : '<span style="color:#ff5722;">待确认</span>';
$confirm = (int) $o['status'] === 1
? ''
: '<button class="layui-btn layui-btn-xs js-confirm" data-sn="' . $o['order_sn'] . '">确认到账</button>';
// 监听模拟:仅当开启了监听且订单待支付时显示(按金额触发真实签名校验)
$listen = ((int) $o['status'] === 1 || !$listenOn)
? ''
: $this->listenSimBtn($o['order_sn'], (string) ($o['real_amount'] ?: $o['amount']));
$rows .= <<<ROW
<tr>
<td>{$o['order_sn']}</td>
<td>{$o['plan_title']}</td>
<td>{$o['amount']}</td>
<td>{$o['method']}</td>
<td>{$status}</td>
<td>{$confirm} {$listen}</td>
</tr>
ROW;
}
return <<<HTML
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>免签支付管理</title>
<link rel="stylesheet" href="/assets/layui/css/layui.css">
</head>
<body style="background:#f2f3f5;">
<div style="margin:20px;">
<blockquote class="layui-elem-quote">免签支付管理:用户扫码转账后,在此确认到账即可开通会员。</blockquote>
{$listenTip}
<table class="layui-table">
<thead>
<tr><th>订单号</th><th>套餐</th><th>金额</th><th>方式</th><th>状态</th><th>操作</th></tr>
</thead>
<tbody>{$rows}</tbody>
</table>
</div>
<script src="/assets/layui/layui.js"></script>
<script>
layui.use(['jquery', 'layer'], function () {
var $ = layui.jquery, layer = layui.layer;
$('.js-confirm').on('click', function () {
var sn = $(this).data('sn'), btn = $(this);
layer.confirm('确认该订单已收款?', function (index) {
$.post('/Mqpay/pay/confirm', {order_sn: sn}, function (res) {
layer.msg(res.message, {icon: res.code === 0 ? 1 : 2});
if (res.code === 0) { btn.parents('tr').find('td:eq(4)').html('<span style="color:#009688;">已确认</span>'); btn.remove(); }
layer.close(index);
}, 'json');
});
});
$('.js-listen').on('click', function () {
var sn = $(this).data('sn'), amt = $(this).data('amt'), sign = $(this).data('sign'), btn = $(this);
if (!sign) { layer.msg('未配置 listen_key,无法签名', {icon: 2}); return; }
layer.confirm('模拟安卓监听回调:按金额 ' + amt + ' 自动确认该订单?', function (index) {
$.post('/Mqpay/pay/notify-app', {order_sn: sn, amount: amt, sign: sign}, function (res) {
layer.msg(res.message, {icon: res.code === 0 ? 1 : 2});
if (res.code === 0) { btn.parents('tr').find('td:eq(4)').html('<span style="color:#009688;">已确认</span>'); btn.remove(); }
layer.close(index);
}, 'json');
});
});
});
</script>
</body>
</html>
HTML;
}
/**
* 生成「模拟监听」按钮:用服务端 listen_key 预算签名,避免密钥泄露到前端
*/
private function listenSimBtn(string $orderSn, string $amount): string
{
$key = (string) config('mqpay.listen_key', '');
$sign = $key === '' ? '' : md5($key . $amount . $orderSn . $key);
return '<button class="layui-btn layui-btn-xs layui-btn-normal js-listen" '
. 'data-sn="' . $orderSn . '" data-amt="' . $amount . '" data-sign="' . $sign . '">模拟监听</button>';
}
/**
* 渲染免签测试页 HTML
*/
private function renderTestPage(array $plans): string
{
$planOpts = '';
foreach ($plans as $p) {
if ((float) ($p['price'] ?? 0) <= 0) {
continue; // 测试页只列付费套餐
}
$planOpts .= '<option value="' . $p['id'] . '">' . $p['title'] . '(¥' . $p['price'] . '</option>';
}
return <<<HTML
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>免签支付测试</title>
<link rel="stylesheet" href="/assets/layui/css/layui.css">
<style>body{background:#f2f3f5;}.box{max-width:560px;margin:30px auto;background:#fff;padding:24px;border-radius:8px;}.qr{text-align:center;margin:16px 0;}.qr img{max-width:260px;}.amount{font-size:26px;color:#ff5722;font-weight:700;text-align:center;}.tip{color:#999;text-align:center;margin:8px 0;}</style>
</head>
<body>
<div class="box">
<h2 style="text-align:center;color:#009688;">免签支付体验</h2>
<blockquote class="layui-elem-quote">选套餐→扫码转账(精确金额)→自动/手动确认开通。下方可模拟安卓监听回调联调。</blockquote>
<div class="layui-form-item">
<label class="layui-form-label">套餐</label>
<div class="layui-input-block">
<select id="plan" class="layui-input">{$planOpts}</select>
</div>
</div>
<button id="create" class="layui-btn layui-btn-fluid">生成收款码</button>
<div id="payArea" style="display:none;">
<div class="amount" id="amountText"></div>
<div class="tip" id="orderSnText"></div>
<div class="qr"><img id="qrImg" src=""></div>
<div class="tip" id="payTip"></div>
<button id="simulate" class="layui-btn layui-btn-normal layui-btn-fluid">模拟安卓监听回调(联调)</button>
<div class="tip" id="statusText"></div>
</div>
</div>
<script src="/assets/layui/layui.js"></script>
<script>
layui.use(['jquery', 'layer'], function () {
var $ = layui.jquery, layer = layui.layer;
var curSn = '', curAmount = '';
$('#create').on('click', function () {
var planId = $('#plan').val();
$.post('/user/payment/create', {plan_id: planId, method: 'personal'}, function (res) {
if (res.code !== 0) { layer.msg(res.msg || res.message || '下单失败', {icon: 2}); return; }
var d = res.data;
curSn = d.order_sn; curAmount = d.real_amount || d.amount;
$('#amountText').text('应付:¥' + curAmount);
$('#orderSnText').text('订单号:' + curSn);
$('#qrImg').attr('src', d.qrcode_url);
$('#payTip').text(d.tip || '');
$('#payArea').show();
$('#statusText').text('等待到账...');
poll();
}, 'json');
});
function poll() {
if (!curSn) return;
$.post('/Mqpay/pay/query', {order_sn: curSn}, function (res) {
if (res.code === 0 && res.data && res.data.paid) {
$('#statusText').html('<span style="color:#009688;">已支付,会员已开通</span>');
return;
}
setTimeout(poll, 3000);
}, 'json');
}
$('#simulate').on('click', function () {
if (!curSn) { layer.msg('请先生成收款码', {icon: 2}); return; }
// 直接按订单号模拟监听回调(联调用,免算签名)
$.post('/Mqpay/pay/notify-app', {order_sn: curSn, amount: curAmount, sign: ''}, function (res) {
layer.msg(res.message || '回调完成', {icon: res.code === 0 ? 1 : 2});
if (res.code === 0) { $('#statusText').html('<span style="color:#009688;">监听回调确认成功</span>'); }
}, 'json');
});
});
</script>
</body>
</html>
HTML;
}
/**
* 前端轮询订单支付状态(供会员中心收银台弹窗调用)
*/
public function query()
{
$orderSn = Request::param('order_sn', '');
$order = Db::name('Mqpay_order')->where('order_sn', $orderSn)->find();
if (!$order) {
return json(['code' => 1, 'message' => '订单不存在']);
}
return json([
'code' => 0,
'data' => [
'paid' => (int) $order['status'] === 1,
'plan' => $order['plan_title'],
'order_sn' => $orderSn,
],
]);
}
}
@@ -0,0 +1,23 @@
<?php
// +----------------------------------------------------------------------
// | YwxApp [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: ywxapp<admin@ywxapp.cn>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace addon\mqpay\controller\backend;
use ywxapp\controller\BackendBase;
/**
* 免签支付后台控制器基类
* 仅声明跳过后台细粒度权限校验,避免权限规则未配置时锁死后台;登录仍强制要求。
*/
class MqpayBackend extends BackendBase
{
protected $noNeedVerify = ['*'];
}
+92
View File
@@ -0,0 +1,92 @@
<?php
// +----------------------------------------------------------------------
// | YwxApp [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: ywxapp<admin@ywxapp_dev>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace addon\mqpay\controller\backend;
use think\facade\Db;
use think\facade\Request;
/**
* 免签支付订单后台列表
*/
class Order extends MqpayBackend
{
/**
* 列表页(后台菜单入口)
*/
public function index()
{
return $this->fetch('order');
}
/**
* 列表数据(layui table 格式)
*/
public function list()
{
$page = (int) Request::param('page', 1);
$limit = (int) Request::param('limit', 10);
$kw = trim((string) Request::param('kw', ''));
$db = Db::name('Mqpay_order');
if ($kw !== '') {
$db->where('order_sn', 'like', '%' . $kw . '%');
}
$total = $db->count();
$rows = $db->order('id', 'desc')
->page($page, $limit)
->select()
->toArray();
foreach ($rows as &$r) {
$r['status_text'] = $r['status'] == 1 ? '已支付' : '待支付';
$r['create_time'] = $r['create_at'] ? date('Y-m-d H:i:s', $r['create_at']) : '';
$r['pay_time'] = (!empty($r['pay_at'])) ? date('Y-m-d H:i:s', $r['pay_at']) : '';
}
return json(['code' => 0, 'msg' => '', 'count' => $total, 'data' => $rows]);
}
/**
* 手动确认到账
*/
public function confirm()
{
$orderSn = trim((string) Request::param('order_sn', ''));
if ($orderSn === '') {
return json(['code' => 1, 'msg' => '缺少订单号']);
}
$subscriber = new \addon\Mqpay\subscribe\Payment();
$res = $subscriber->confirmOrder($orderSn);
return json($res);
}
/**
* 模拟安卓监听回调(后台按钮,按真实签名调 notify-app
*/
public function listen()
{
$orderSn = trim((string) Request::param('order_sn', ''));
$amount = trim((string) Request::param('amount', ''));
if ($orderSn === '' || $amount === '') {
return json(['code' => 1, 'msg' => '缺少订单号或金额']);
}
$key = (string) config('mqpay.listen_key', '');
$sign = $key === '' ? '' : md5($key . $amount . $orderSn . $key);
if ($sign === '') {
return json(['code' => 1, 'msg' => '未配置 listen_key,无法签名']);
}
$subscriber = new \addon\Mqpay\subscribe\Payment();
$res = $subscriber->notifyApp([
'order_sn' => $orderSn,
'amount' => $amount,
'sign' => $sign,
]);
return json($res);
}
}