Files
YwxAppThink/app/member/controller/Payment.php
T

210 lines
7.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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);
}
}