Files

702 lines
29 KiB
PHP
Raw Permalink 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 AppMarket [ 应用市场中心站插件 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: ywxapp<admin@ywxapp.cn>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace addon\appmall\service;
use addon\appmall\model\AddonDownloadLog;
use addon\appmall\model\AddonLicense;
use addon\appmall\model\AddonOrder;
use think\facade\Cache;
use think\facade\Db;
use think\facade\Log;
use think\Request;
use ywxapp\model\BaseModel;
use Yansongda\Pay\Pay;
/**
* 市场中心站本地成交域服务(会员直购 / 授权 / 收益 / 下载)。
*
* 由核心 app/api/controller/v1/Addon.php 在「本机即中心站」模式下进程内委托调用;
* 所有中心站领域逻辑(订单、授权、收益账本、支付回调)收敛于插件,核心不再持有。
*
* 约定:业务失败抛 MarketException(携带 HTTP 语义 code),由调用方转成 Result 响应。
*/
class MarketService
{
protected static ?MarketService $instance = null;
public static function instance(): static
{
if (static::$instance === null) {
static::$instance = new static();
// 中心站成交域首次被委托时,确保全部 appmall 表存在(覆盖 v1/Addon 等直接调用方)
\addon\appmall\library\AppmallSchema::ensure();
}
return static::$instance;
}
/**
* 运行时自愈建表(仅当表不存在时创建),保证购买闭环在当前库即可跑通。
* 已收敛到 \addon\appmall\library\AppmallSchema::ensure()(建全部表 + 扩展列),此处委托。
*/
public function ensureTables(): void
{
\addon\appmall\library\AppmallSchema::ensure();
}
/**
* 商品查询(appmall_addon_list
*/
public function product(int $id): ?array
{
return Db::name('appmall_addon_list')->where('id', $id)->where('status', 1)->find();
}
/**
* 插件详情(含当前用户已购状态)
* @throws MarketException 404 插件不存在或已下架
*/
public function info(int $id, int $uid = 0): array
{
$addon = $this->product($id);
if (!$addon) {
throw new MarketException('插件不存在或已下架', 404);
}
$purchased = false;
if ($uid > 0) {
$purchased = AddonLicense::where('uid', $uid)->where('aid', $id)->count() > 0;
}
return [
'addon' => [
'id' => $addon['id'],
'name' => $addon['name'],
'title' => $addon['title'],
'version' => $addon['version'],
'price' => $addon['price'],
'logo' => $addon['logo'],
'intro' => $addon['description'] ?? '',
'author' => $addon['author'] ?? '',
],
'purchased' => $purchased,
];
}
/**
* 上架插件分页列表
* @return array{items: array, total: int}
*/
public function lists(int $page = 1, int $perPage = 15): array
{
$perPage = min(max($perPage, 1), 50);
$page = max($page, 1);
$query = Db::name('appmall_addon_list')->where('status', 1);
$total = (clone $query)->count();
$items = $query->field('id,name,title,version,price,logo,description as intro,author')
->order('id', 'desc')
->page($page, $perPage)
->select()
->toArray();
return ['items' => $items, 'total' => $total];
}
/**
* 发起购买:创建/复用待支付订单并拉起支付(或模拟支付直接签发授权)。
* @param string $baseUrl 客户端根地址(用于拼支付回调)
* @return array{data: array, message: string}
* @throws MarketException
*/
public function buy(int $uid, int $addonId, string $method, string $baseUrl): array
{
$addon = $this->product($addonId);
if (!$addon) {
throw new MarketException('插件不存在或已下架', 404);
}
// 免费插件无需购买(服务端 market valid 也直接放行)
if ((float) $addon['price'] <= 0) {
return ['data' => ['free' => true], 'message' => '该插件免费,可直接安装'];
}
// 幂等:已购直接返回授权
$existLicense = AddonLicense::where('uid', $uid)->where('aid', $addonId)->find();
if ($existLicense) {
return ['data' => ['license_key' => $existLicense->license_key], 'message' => '您已购买该插件'];
}
// 防重复下单:复用 15 分钟内待支付订单
$order = AddonOrder::where('uid', $uid)
->where('aid', $addonId)
->where('status', 0)
->where('create_at', '>', time() - 900)
->find();
if (!$order) {
$order = AddonOrder::create([
'uid' => $uid,
'aid' => $addonId,
'amount' => $addon['price'],
'status' => 0,
'trade_no' => date('YmdHis') . $uid . $addonId . random_int(100000, 999999),
]);
}
// 模拟支付:无真实商户号时直接完成并签发授权(仅本地/自托管调试用)
if (config('pay.mock_enable')
&& empty(config('pay.alipay.app_id'))
&& empty(config('pay.wechat.mch_id'))) {
$this->completeOrder($order, $addonId, $uid);
$license = AddonLicense::where('uid', $uid)->where('aid', $addonId)->find();
return [
'data' => ['license_key' => $license?->license_key, 'mock' => true],
'message' => '(模拟支付)购买成功',
];
}
try {
$base = rtrim($baseUrl, '/');
$payParams = [
'out_trade_no' => $order->trade_no,
'total_amount' => $order->amount,
'subject' => '购买插件:' . $addon['title'],
];
if ($method === 'wechat') {
$wconfig = config('pay.wechat');
if (empty($wconfig['mch_id']) || empty($wconfig['app_id'])) {
throw new \Exception('微信支付未配置,请联系管理员');
}
$wconfig['notify_url'] = $base . '/v1/addon/notify';
$result = Pay::wechat($wconfig)->h5($payParams);
return [
'data' => ['order_no' => $order->trade_no, 'pay_params' => $result->getBody()->getContents()],
'message' => '支付发起成功',
];
}
$aconfig = config('pay.alipay');
if (empty($aconfig['app_id']) || empty($aconfig['private_key'])) {
throw new \Exception('支付未配置,请联系管理员');
}
$aconfig['notify_url'] = $base . '/v1/addon/notify';
$aconfig['return_url'] = $base . '/v1/addon/payResult';
$result = Pay::alipay($aconfig)->web($payParams);
return [
'data' => ['order_no' => $order->trade_no, 'pay_params' => $result->getBody()->getContents()],
'message' => '支付发起成功',
];
} catch (\Throwable $e) {
throw new MarketException('支付发起失败: ' . $e->getMessage(), 500);
}
}
/**
* 订单支付成功后:置已付 + 幂等签发授权 + 写入开发者收益账本(分红)
*/
public function completeOrder($order, int $aid, int $uid): void
{
if ($order->status == 0) {
$order->status = 1;
$order->pay_time = time();
$order->save();
}
$exist = AddonLicense::where('uid', $uid)->where('aid', $aid)->find();
if (!$exist) {
try {
AddonLicense::create([
'uid' => $uid,
'aid' => $aid,
'license_key' => $this->generateLicenseKey($uid, $aid),
'expire_time' => 0,
'download_count' => 0,
]);
} catch (\PDOException $e) {
if (stripos($e->getMessage(), 'duplicate') === false
&& stripos($e->getMessage(), '1062') === false) {
throw $e;
}
}
}
// 写入收益账本(仅当该商品归属某开发者)
try {
$product = Db::name('appmall_addon_list')->where('id', $aid)->find();
$devId = (int) ($product['developer_id'] ?? 0);
if ($devId > 0 && !Db::name('appmall_addon_revenues')->where('order_id', $order->id)->find()) {
$rate = (float) config('appmall.commission_rate', 0.2);
$commission = round((float) $order->amount * $rate, 2);
$income = round((float) $order->amount - $commission, 2);
Db::name('appmall_addon_revenues')->insert([
'developer_id' => $devId,
'order_id' => $order->id,
'aid' => $aid,
'uid' => $uid,
'amount' => $order->amount,
'commission' => $commission,
'income' => $income,
'status' => 0,
'create_at' => time(),
]);
// 计入开发者可提现余额(提现申请时再转入冻结待打款)
Db::name('appmall_developers')->where('id', $devId)->inc('balance', $income)->update();
}
} catch (\Exception $e) {
// 收益记账失败不影响已完成的购买
Log::warning('收益记账失败:' . $e->getMessage());
}
// 钩子点:订单支付完成(已签发授权 + 写入分红账本)后触发,
// 插件可在 info.php['events']['listen']['order_paid'] 监听,用于发货 / 通知等联动。
event('order_paid', ['order' => $order, 'aid' => $aid, 'uid' => $uid]);
}
/**
* 支付结果查询(前端轮询用)
* @throws MarketException 404 订单不存在
*/
public function orderStatus(int $uid, string $tradeNo): array
{
$order = AddonOrder::where('trade_no', $tradeNo)->where('uid', $uid)->find();
if (!$order) {
throw new MarketException('订单不存在', 404);
}
$data = ['status' => $order->status];
if ($order->status === 1) {
$license = AddonLicense::where('uid', $uid)->where('aid', $order->aid)->find();
$data['license_key'] = $license?->license_key;
}
return $data;
}
/**
* 支付完成落地页状态(支付宝 return_url 跳转,无需登录,仅展示状态)
* @return array{data: array, message: string}
*/
public function payResult(string $tradeNo): array
{
if ($tradeNo === '') {
return ['data' => [], 'message' => '支付流程结束'];
}
$order = AddonOrder::where('trade_no', $tradeNo)->find();
if (!$order) {
return ['data' => ['status' => 0], 'message' => '订单不存在'];
}
return ['data' => ['status' => $order->status], 'message' => '获取成功'];
}
/**
* 退款 / 吊销授权(买家或运营)
* @return array{data: array, message: string}
* @throws MarketException
*/
public function refund(int $uid, string $tradeNo, int $orderId): array
{
$order = null;
if ($tradeNo !== '') {
$order = AddonOrder::where('trade_no', $tradeNo)->where('uid', $uid)->find();
} elseif ($orderId > 0) {
$order = AddonOrder::where('id', $orderId)->where('uid', $uid)->find();
}
if (!$order) {
throw new MarketException('订单不存在', 404);
}
if ($order->status === 2) {
return ['data' => ['status' => 2], 'message' => '该订单已退款'];
}
if ($order->status !== 1) {
throw new MarketException('仅已支付订单可申请退款', 400);
}
Db::transaction(function () use ($order, $uid) {
$order->status = 2;
$order->save();
// 吊销授权(status=0 且过期,download/valid 双重拦截)
AddonLicense::where('uid', $uid)->where('aid', $order->aid)
->update(['status' => 0, 'expire_time' => time()]);
// 收益冲正
$rev = Db::name('appmall_addon_revenues')->where('order_id', $order->id)->find();
if ($rev) {
if ((int) $rev['status'] === 0) {
Db::name('appmall_developers')->where('id', $rev['developer_id'])
->dec('balance', (float) $rev['income'])->update();
}
Db::name('appmall_addon_revenues')->where('id', $rev['id'])->update(['status' => 2]);
}
});
return ['data' => ['status' => 2], 'message' => '退款成功,授权已吊销'];
}
/**
* 我的已购插件(买家视角):列出已购付费插件 + 授权状态 + 是否可退款
*/
public function mine(int $uid): array
{
$rows = Db::name('appmall_addon_licenses')
->alias('l')
->join('appmall_addon_list a', 'a.id = l.aid')
->where('l.uid', $uid)
->where('a.price', '>', 0)
->field('a.name,a.title,a.version,a.price,l.status as license_status,l.expire_time,l.domain,l.aid')
->order('l.id', 'desc')
->select()
->toArray();
$list = [];
foreach ($rows as $r) {
$order = Db::name('appmall_addon_orders')->where('uid', $uid)->where('aid', $r['aid'])
->order('id', 'desc')->find();
$orderStatus = $order ? (int) $order['status'] : 0;
$refundable = ((int) $r['license_status'] === 1) && $orderStatus === 1;
$list[] = [
'name' => $r['name'],
'title' => $r['title'],
'version' => $r['version'],
'price' => $r['price'],
'license_status' => (int) $r['license_status'],
'expire_time' => (int) $r['expire_time'],
'domain' => $r['domain'] ?? '',
'order_id' => $order ? (int) $order['id'] : 0,
'trade_no' => $order['trade_no'] ?? '',
'refundable' => $refundable,
];
}
return ['list' => $list];
}
/**
* 支付宝/微信异步回调:验签 -> 校验金额 -> 订单置已付 -> 生成授权
* @return \think\Response
*/
public function notify(string $method)
{
try {
if ($method === 'wechat') {
$pay = Pay::wechat(config('pay.wechat'));
$data = $pay->callback();
$tradeNo = $data->get('out_trade_no');
$amount = (float) $data->get('amount') ?: (float) $data->get('total');
} else {
$pay = Pay::alipay(config('pay.alipay'));
$data = $pay->verify();
$tradeNo = $data->get('out_trade_no');
$amount = (float) $data->get('total_amount');
$tradeStatus = (string) $data->get('trade_status');
if (!in_array($tradeStatus, ['TRADE_SUCCESS', 'TRADE_FINISHED'], true)) {
return $pay->success();
}
}
} catch (\Throwable) {
return response('fail');
}
$order = AddonOrder::where('trade_no', $tradeNo)->find();
if ($order && $order->status == 0) {
// 防金额篡改:回调金额必须与订单一致
if (abs((float) $order->amount - $amount) < 0.01) {
Db::transaction(function () use ($order) {
$order->refresh();
if ($order->status != 0) {
return;
}
$this->completeOrder($order, $order->aid, $order->uid);
});
}
}
return $method === 'wechat' ? response('SUCCESS') : Pay::alipay(config('pay.alipay'))->success();
}
/**
* 用户下载已购插件(需有效授权 + 域名/签名/次数校验)。
* @return array{file: string, filename: string} 通过校验后返回文件路径与下载名
* @throws MarketException
*/
public function download(int $uid, int $addonId, Request $request): array
{
$license = AddonLicense::where('uid', $uid)->where('aid', $addonId)->find();
if (!$license) {
throw new MarketException('您尚未购买该插件', 403);
}
// 授权已吊销(退款):拒绝下载
if (isset($license->status) && (int) $license->status !== 1) {
throw new MarketException('授权已被吊销,无法下载', 403);
}
if ($license->expire_time > 0 && $license->expire_time < time()) {
throw new MarketException('授权已过期', 403);
}
// 域名绑定:license.domain 非空时,仅允许绑定的域名下载
if (!empty($license->domain)) {
$host = $request->host();
$hostNoPort = preg_replace('/:\d+$/', '', $host);
$allowed = array_filter(array_map('trim', preg_split('/[,\s]+/', (string) $license->domain)));
if (!in_array($host, $allowed, true) && !in_array($hostNoPort, $allowed, true)) {
throw new MarketException('该授权未绑定当前域名,无法下载', 403);
}
}
// 下载签名校验(开关 addon_download_sign
$signSecret = config('ywxapp.addon_secret');
if (config('ywxapp.addon_download_sign') && $signSecret) {
$ts = (int) $request->get('ts', 0);
$sign = (string) $request->get('sign', '');
if ($ts <= 0 || time() - $ts > 600) {
throw new MarketException('下载授权已过期,请重新获取', 403);
}
$expected = $license->generateSign($license->license_key, $addonId, $ts, $signSecret);
if (!hash_equals($expected, $sign)) {
throw new MarketException('下载授权校验失败', 403);
}
}
$addon = Db::name('appmall_addon_list')->where('id', $addonId)->where('status', 1)->find();
if (!$addon) {
throw new MarketException('插件不存在或已下架', 404);
}
// 下载次数限制:0 表示不限
if ($license->download_count > 0) {
$used = AddonDownloadLog::where('license_id', $license->id)->count();
if ($used >= $license->download_count) {
throw new MarketException('下载次数已用完', 403);
}
}
$file = realpath($addon['file_path']);
$root = realpath(root_path()) ?: root_path();
if ($file === false || !is_file($file) || strpos($file, $root) !== 0) {
throw new MarketException('插件文件暂不可用,请联系管理员', 404);
}
AddonDownloadLog::create([
'license_id' => $license->id,
'uid' => $uid,
'aid' => $addonId,
'ip' => $request->ip(),
]);
return ['file' => $file, 'filename' => $addon['name'] . '-' . $addon['version'] . '.zip'];
}
/**
* 后台市场目录(中心站本机浏览):关键词/分类筛选 + 排序 + 分类下拉数据源。
* 自愈元数据列(category/tags/screenshots/rating),与 api/Market::ensureAddonListColumns 对齐。
* @return array{list: array, categories: array}
*/
public function catalog(string $keyword = '', string $category = '', string $order = 'new', string $type = ''): array
{
// 自愈元数据列:旧库缺 category/tags/screenshots/rating 时自动补齐
$listTable = Db::name('appmall_addon_list')->getTable();
$colDefs = [
'category' => "varchar(50) DEFAULT '' COMMENT '分类'",
'tags' => "varchar(255) DEFAULT '' COMMENT '标签(逗号分隔)'",
'screenshots' => "text COMMENT '截图URL(逗号分隔或JSON'",
'rating' => "decimal(2,1) DEFAULT '0.0' COMMENT '评分(0.0~5.0'",
'changelog' => "text COMMENT '更新日志'",
'require_framework' => "varchar(20) DEFAULT '' COMMENT '最低兼容框架版本,如 1.2.0'",
'type' => "varchar(20) NOT NULL DEFAULT 'addon' COMMENT '商品类型:addon=插件 template=模板'",
];
foreach ($colDefs as $col => $def) {
BaseModel::ensureColumn($listTable, $col, $def);
}
// 探测元数据列是否存在,存在才选,保证兼容
$hasCols = [];
try {
$cols = Db::query("SHOW COLUMNS FROM `{$listTable}`");
$hasCols = array_column($cols, 'Field');
} catch (\Throwable $e) {
}
$has = fn (string $c): bool => in_array($c, $hasCols, true);
$q = Db::name('appmall_addon_list')->where('status', 1);
if ($keyword !== '') {
$q->where(function ($w) use ($keyword) {
$w->where('name', 'like', '%' . $keyword . '%')
->whereOr('title', 'like', '%' . $keyword . '%')
->whereOr('author', 'like', '%' . $keyword . '%')
->whereOr('description', 'like', '%' . $keyword . '%');
});
}
if ($category !== '' && $has('category')) {
$q->where('category', $category);
}
// 商品类型筛选(addon=插件 / template=模板;空=全部)
if ($type !== '' && $has('type')) {
$q->where('type', $type);
}
$field = 'id,name,title,description as intro,author,version,logo,price,download_count,update_at';
foreach (['category', 'tags', 'screenshots', 'rating', 'changelog', 'require_framework', 'type'] as $mf) {
if ($has($mf)) {
$field .= ',' . $mf;
}
}
// 排序:对齐 api/Market::lists 的 orderMapdownload_count 为商品表内置列)
$orderMap = [
'new' => ['id', 'desc'],
'hot' => ['download_count', 'desc'],
'price_asc' => ['price', 'asc'],
'price_desc' => ['price', 'desc'],
];
[$of, $od] = $orderMap[$order] ?? $orderMap['new'];
$list = $q->field($field)->order($of, $od)->select()->toArray();
// 同名插件多版本聚合:只展示最新版,其余版本收进 versions(详情弹窗版本下拉用)
$list = self::aggregateVersions($list);
// 分类下拉数据源:本机全部非空分类
$categories = [];
if ($has('category')) {
$categories = Db::name('appmall_addon_list')
->where('status', 1)->where('category', '<>', '')
->distinct(true)->order('category', 'asc')->column('category');
}
return ['list' => $list, 'categories' => $categories];
}
/**
* 运营退款(后台特权):按插件名退最新「已支付」订单,
* 吊销授权 + 订单置已退款 + 收益冲正。
* @throws MarketException
*/
public function operatorRefund(string $name): void
{
$addon = Db::name('appmall_addon_list')->where('name', $name)->find();
if (empty($addon)) {
throw new MarketException('插件不存在', 404);
}
$order = Db::name('appmall_addon_orders')->where('aid', $addon['id'])
->where('status', 1)->order('id', 'desc')->find();
if (empty($order)) {
throw new MarketException('未找到该插件的已支付订单', 404);
}
Db::transaction(function () use ($order, $addon) {
Db::name('appmall_addon_orders')->where('id', $order['id'])
->update(['status' => 2, 'update_at' => time()]);
Db::name('appmall_addon_licenses')->where('uid', $order['uid'])
->where('aid', $addon['id'])
->update(['status' => 0, 'expire_time' => time(), 'update_at' => time()]);
$rev = Db::name('appmall_addon_revenues')->where('order_id', $order['id'])->find();
if ($rev) {
if ((int) $rev['status'] === 0) {
Db::name('appmall_developers')->where('id', $rev['developer_id'])
->dec('balance', (float) $rev['income'])->update();
}
Db::name('appmall_addon_revenues')->where('id', $rev['id'])->update(['status' => 2]);
}
});
}
/**
* 同名插件多版本聚合:每个插件只保留最新版一条作代表行,
* 其余版本收进代表行的 versions 数组(含最新版自身,按版本号降序),
* 供市场详情页/弹窗做「版本下拉选择」,列表不再平铺展示多个版本。
* 输出顺序 = 各插件在原列表中的首次出现顺序(尽量保持原排序语义)。
*/
public static function aggregateVersions(array $rows): array
{
$groups = [];
$names = [];
foreach ($rows as $r) {
$n = (string) ($r['name'] ?? '');
if (!isset($groups[$n])) {
$groups[$n] = [];
$names[] = $n;
}
$groups[$n][] = $r;
}
$out = [];
foreach ($names as $n) {
$vers = $groups[$n];
usort($vers, fn ($a, $b) => version_compare((string) ($b['version'] ?? '0'), (string) ($a['version'] ?? '0')));
$main = $vers[0];
$main['versions'] = array_map(fn ($v) => [
'id' => $v['id'] ?? 0,
'version' => (string) ($v['version'] ?? ''),
'price' => $v['price'] ?? 0,
'update_at' => (int) ($v['update_at'] ?? 0),
], $vers);
$out[] = $main;
}
return $out;
}
/**
* 修改已上架插件价格(按插件名维度,同步全部版本行 + 提交记录)。
* 后台改价传 developerId=0(不限归属);开发者中心改价须传本人 developerId(仅能改自己的应用)。
* 改价后 bump 目录版本号,市场列表缓存立即失效。
* @return int 受影响的版本行数
* @throws MarketException
*/
public function updatePrice(string $name, float $price, int $developerId = 0): int
{
if ($name === '') {
throw new MarketException('缺少插件标识', 400);
}
if ($price < 0 || $price > 999999) {
throw new MarketException('价格必须在 0 ~ 999999 之间', 400);
}
$q = Db::name('appmall_addon_list')->where('name', $name);
if ($developerId > 0) {
$q->where('developer_id', $developerId);
}
$rows = $q->select()->toArray();
if (empty($rows)) {
// 尚未上架(仅有提交记录)也允许改价:只同步 submissions
$subExist = Db::name('appmall_addon_submissions')->where('name', $name)
->when($developerId > 0, fn ($w) => $w->where('developer_id', $developerId))
->count();
if ($subExist <= 0) {
throw new MarketException($developerId > 0 ? '未找到你名下的该插件' : '插件不存在', 404);
}
}
$ids = array_column($rows, 'id');
if ($ids) {
Db::name('appmall_addon_list')->whereIn('id', $ids)
->update(['price' => $price, 'update_at' => time()]);
}
// 同步提交记录价格(保持审核台/开发者中心口径一致;仅同步同归属记录)
try {
$sq = Db::name('appmall_addon_submissions')->where('name', $name);
if ($developerId > 0) {
$sq->where('developer_id', $developerId);
}
$sq->update(['price' => $price, 'update_at' => time()]);
} catch (\Throwable $e) {
// 提交记录同步失败不影响商品改价
}
self::bumpCatalogVersion();
return count($ids);
}
/**
* 目录版本号 bump:使 api/Market::lists 的 60s 缓存立即失效(审核发布/改价后调用)。
*/
public static function bumpCatalogVersion(): void
{
try {
Cache::set('appmall:catalog:ver', (string) microtime(true));
} catch (\Throwable $e) {
// 缓存不可用时静默(列表最多延迟 60s 更新)
}
}
/**
* 生成唯一授权码
*/
protected function generateLicenseKey(int $uid, int $aid): string
{
do {
$key = strtoupper(sprintf(
'%s-%s-%s',
substr(md5($uid . $aid . random_bytes(8)), 0, 8),
bin2hex(random_bytes(4)),
bin2hex(random_bytes(4))
));
} while (AddonLicense::where('license_key', $key)->find());
return $key;
}
}