chore: 重写初始提交(清空历史,整理后全量提交)
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
<?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 app\member\controller;
|
||||
|
||||
use think\facade\Event;
|
||||
use think\Request;
|
||||
use ywxapp\controller\MemberBase;
|
||||
use app\member\validate\Payment as PaymentValidate;
|
||||
|
||||
/**
|
||||
* 支付控制器
|
||||
*
|
||||
* 设计原则:核心只负责「订单/流程编排 + 事件触发」,具体支付方式由【支付插件】实现,
|
||||
* 通过监听以下事件注入能力(插件在 info.php 的 events 中声明监听器即可):
|
||||
*
|
||||
* - PaymentMethods 收集可用支付方式。监听器返回 array,如:
|
||||
* [['code'=>'wechat','name'=>'微信支付','desc'=>'...','icon'=>'','sort'=>0], ...]
|
||||
* - PaymentCreate 生成支付参数。监听器接收 ['order'=>[...],'params'=>[...]],
|
||||
* 返回网关参数 array(按需返回 null 表示不匹配该方法),如:
|
||||
* ['type'=>'qrcode','qrcode_url'=>'weixin://...','expire'=>600]
|
||||
* ['type'=>'redirect','redirect_url'=>'https://...']
|
||||
* ['type'=>'form','html'=>'<form ...>']
|
||||
* - PaymentNotify 异步回调。监听器接收 ['method'=>...,'request'=>[...],'get'=>[...]],
|
||||
* 完成验签与订单激活后,返回网关要求的响应字符串(如 'success')。
|
||||
* - PaymentFreeActive 免费套餐开通钩子(无支付)。
|
||||
* - PaymentOrderCreate 订单落库钩子(插件可在此持久化订单)。
|
||||
*
|
||||
* 未安装任何支付插件时,create() 会返回「暂无可用支付方式」提示。
|
||||
*/
|
||||
class Payment extends MemberBase
|
||||
{
|
||||
/**
|
||||
* 套餐配置(来自 config/member.php,价格单位:元,duration 单位:天)
|
||||
* @var array
|
||||
*/
|
||||
private $plans = [];
|
||||
|
||||
/**
|
||||
* 支付页 / 套餐列表(免登录可看)
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedLogin = ['index', 'methods'];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedVerify = ['*'];
|
||||
|
||||
|
||||
protected function initialize()
|
||||
{
|
||||
$this->plans = config('member.plans', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付页面
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->view->assign('plans', array_values($this->plans));
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用支付方式(插件通过 PaymentMethods 事件注入)
|
||||
*/
|
||||
public function methods()
|
||||
{
|
||||
$results = Event::trigger('PaymentMethods', []);
|
||||
$methods = [];
|
||||
$codes = [];
|
||||
foreach ((array)$results as $r) {
|
||||
if (!is_array($r)) {
|
||||
continue;
|
||||
}
|
||||
foreach (array_values($r) as $m) {
|
||||
if (!is_array($m) || empty($m['code']) || in_array($m['code'], $codes, true)) {
|
||||
continue;
|
||||
}
|
||||
$codes[] = $m['code'];
|
||||
$methods[] = $m;
|
||||
}
|
||||
}
|
||||
// 按 sort 排序
|
||||
usort($methods, function ($a, $b) {
|
||||
return ($a['sort'] ?? 0) <=> ($b['sort'] ?? 0);
|
||||
});
|
||||
// 兜底:若没有任何插件注入支付方式(如插件系统未初始化),至少提供「个人免签」体验方式
|
||||
if (empty($methods)) {
|
||||
$methods[] = [
|
||||
'code' => 'personal',
|
||||
'name' => '个人免签',
|
||||
'desc' => '扫码转账到个人收款码,站长确认到账后开通',
|
||||
'icon' => '',
|
||||
'sort' => 0,
|
||||
];
|
||||
}
|
||||
$this->result->success($methods);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建支付订单
|
||||
* 1) 免费套餐:直接触发 PaymentFreeActive,无需支付
|
||||
* 2) 付费套餐:触发 PaymentOrderCreate(落库钩子)+ PaymentCreate(网关参数钩子)
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
if (!($this->request->isAjax() && $this->request->isPost())) {
|
||||
$this->result->error('访问错误');
|
||||
}
|
||||
$user = $this->auth->model;
|
||||
$planId = (int)$this->request->param('plan_id', 0);
|
||||
$method = (string)$this->request->param('method', '');
|
||||
|
||||
$validate = new PaymentValidate();
|
||||
if (! $validate->check(['plan_id' => $planId, 'method' => $method])) {
|
||||
$this->result->error($validate->getError());
|
||||
}
|
||||
$plan = $this->getPlan($planId);
|
||||
if (!$plan) {
|
||||
$this->result->error('套餐不存在', 1);
|
||||
}
|
||||
|
||||
// 免费套餐直接开通
|
||||
if ($plan['price'] <= 0) {
|
||||
Event::trigger('PaymentFreeActive', ['member' => $user, 'plan' => $plan]);
|
||||
$this->result->success(['type' => 'free', 'plan' => $plan['title']]);
|
||||
}
|
||||
|
||||
if (!$method) {
|
||||
$this->result->error('请选择支付方式', 1);
|
||||
}
|
||||
|
||||
$order = [
|
||||
'order_sn' => $this->genOrderSn($user->uid),
|
||||
'uid' => $user->uid,
|
||||
'plan_id' => $planId,
|
||||
'plan_title' => $plan['title'],
|
||||
'amount' => $plan['price'],
|
||||
'method' => $method,
|
||||
'create_at' => time(),
|
||||
];
|
||||
|
||||
// 订单落库钩子(插件可持久化订单)
|
||||
Event::trigger('PaymentOrderCreate', ['order' => $order]);
|
||||
|
||||
// 生成支付参数:由各支付插件监听 PaymentCreate 返回网关数据
|
||||
$results = Event::trigger('PaymentCreate', [
|
||||
'order' => $order,
|
||||
'params' => $this->request->param(),
|
||||
]);
|
||||
$gateway = null;
|
||||
foreach ((array)$results as $r) {
|
||||
if (is_array($r) && !empty($r)) {
|
||||
$gateway = $r;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (empty($gateway)) {
|
||||
$this->result->error('暂无可用支付方式,请先安装支付插件', 1);
|
||||
}
|
||||
$this->result->success($gateway);
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步回调(由各支付插件处理具体网关通知)
|
||||
* 插件监听 PaymentNotify 完成验签与订单激活,并返回网关要求的响应体。
|
||||
*/
|
||||
public function notify()
|
||||
{
|
||||
$method = (string)$this->request->param('method', '');
|
||||
$results = Event::trigger('PaymentNotify', [
|
||||
'method' => $method,
|
||||
'request' => $this->request->param(),
|
||||
'get' => $this->request->get(),
|
||||
'post' => $this->request->post(),
|
||||
]);
|
||||
foreach ((array)$results as $r) {
|
||||
if (is_string($r) && $r !== '') {
|
||||
return $r;
|
||||
}
|
||||
}
|
||||
return 'success';
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据套餐 id 获取套餐配置
|
||||
*/
|
||||
private function getPlan(int $id): ?array
|
||||
{
|
||||
return $this->plans[$id] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成订单号
|
||||
*/
|
||||
private function genOrderSn(int $uid): string
|
||||
{
|
||||
return date('YmdHis') . $uid . mt_rand(100, 999);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user