2024-01-03 17:02:18 +00:00
|
|
|
|
<?php
|
|
|
|
|
|
2024-01-11 16:52:55 +00:00
|
|
|
|
namespace App\Services;
|
2024-01-03 17:02:18 +00:00
|
|
|
|
|
2024-01-05 16:42:50 +00:00
|
|
|
|
use App\Exceptions\FailedToActivateLicenseException;
|
2024-03-22 13:40:52 +00:00
|
|
|
|
use App\Http\Integrations\LemonSqueezy\LemonSqueezyConnector;
|
|
|
|
|
use App\Http\Integrations\LemonSqueezy\Requests\ActivateLicenseRequest;
|
|
|
|
|
use App\Http\Integrations\LemonSqueezy\Requests\DeactivateLicenseRequest;
|
|
|
|
|
use App\Http\Integrations\LemonSqueezy\Requests\ValidateLicenseRequest;
|
2024-01-05 16:42:50 +00:00
|
|
|
|
use App\Models\License;
|
2024-02-23 18:36:02 +00:00
|
|
|
|
use App\Services\License\Contracts\LicenseServiceInterface;
|
2024-01-05 16:42:50 +00:00
|
|
|
|
use App\Values\LicenseInstance;
|
|
|
|
|
use App\Values\LicenseMeta;
|
|
|
|
|
use App\Values\LicenseStatus;
|
|
|
|
|
use Illuminate\Contracts\Encryption\DecryptException;
|
2024-01-11 16:52:55 +00:00
|
|
|
|
use Illuminate\Http\Response;
|
2024-01-05 16:42:50 +00:00
|
|
|
|
use Illuminate\Support\Facades\Cache;
|
|
|
|
|
use Illuminate\Support\Facades\Log;
|
2024-03-22 13:40:52 +00:00
|
|
|
|
use Saloon\Exceptions\Request\RequestException;
|
2024-01-05 16:42:50 +00:00
|
|
|
|
use Throwable;
|
|
|
|
|
|
2024-01-09 18:34:40 +00:00
|
|
|
|
class LicenseService implements LicenseServiceInterface
|
2024-01-03 17:02:18 +00:00
|
|
|
|
{
|
2024-04-18 14:36:28 +00:00
|
|
|
|
public function __construct(private readonly LemonSqueezyConnector $connector, private readonly string $hashSalt)
|
2024-01-05 16:42:50 +00:00
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-08 13:02:26 +00:00
|
|
|
|
public function activate(string $key): License
|
2024-01-05 16:42:50 +00:00
|
|
|
|
{
|
|
|
|
|
try {
|
2024-03-22 13:40:52 +00:00
|
|
|
|
$result = $this->connector->send(new ActivateLicenseRequest($key))->object();
|
2024-01-05 16:42:50 +00:00
|
|
|
|
|
2024-03-22 13:40:52 +00:00
|
|
|
|
if ($result->meta->store_id !== config('lemonsqueezy.store_id')) {
|
2024-01-07 19:02:05 +00:00
|
|
|
|
throw new FailedToActivateLicenseException('This license key is not from Koel’s official store.');
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-22 13:40:52 +00:00
|
|
|
|
$license = $this->updateOrCreateLicenseFromApiResponseBody($result);
|
2024-01-11 16:52:55 +00:00
|
|
|
|
$this->cacheStatus(LicenseStatus::valid($license));
|
|
|
|
|
|
|
|
|
|
return $license;
|
2024-03-22 13:40:52 +00:00
|
|
|
|
} catch (RequestException $e) {
|
|
|
|
|
throw FailedToActivateLicenseException::fromRequestException($e);
|
2024-01-05 16:42:50 +00:00
|
|
|
|
} catch (Throwable $e) {
|
2024-01-08 13:02:26 +00:00
|
|
|
|
Log::error($e);
|
2024-01-11 16:52:55 +00:00
|
|
|
|
throw FailedToActivateLicenseException::fromThrowable($e);
|
2024-01-05 16:42:50 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-09 18:34:40 +00:00
|
|
|
|
public function deactivate(License $license): void
|
2024-01-08 13:02:26 +00:00
|
|
|
|
{
|
|
|
|
|
try {
|
2024-03-22 13:40:52 +00:00
|
|
|
|
$result = $this->connector->send(new DeactivateLicenseRequest($license))->object();
|
2024-01-08 13:02:26 +00:00
|
|
|
|
|
2024-03-22 13:40:52 +00:00
|
|
|
|
if ($result->deactivated) {
|
2024-01-11 16:52:55 +00:00
|
|
|
|
self::deleteLicense($license);
|
|
|
|
|
}
|
2024-03-22 13:40:52 +00:00
|
|
|
|
} catch (RequestException $e) {
|
|
|
|
|
if ($e->getStatus() === Response::HTTP_NOT_FOUND) {
|
2024-01-11 16:52:55 +00:00
|
|
|
|
// The instance ID was not found. The license record must be a leftover from an erroneous attempt.
|
|
|
|
|
self::deleteLicense($license);
|
|
|
|
|
|
|
|
|
|
return;
|
2024-01-08 13:02:26 +00:00
|
|
|
|
}
|
2024-01-11 16:52:55 +00:00
|
|
|
|
|
2024-03-22 13:40:52 +00:00
|
|
|
|
throw FailedToActivateLicenseException::fromRequestException($e);
|
2024-01-08 13:02:26 +00:00
|
|
|
|
} catch (Throwable $e) {
|
|
|
|
|
Log::error($e);
|
|
|
|
|
throw $e;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getStatus(bool $checkCache = true): LicenseStatus
|
2024-01-05 16:42:50 +00:00
|
|
|
|
{
|
|
|
|
|
if ($checkCache && Cache::has('license_status')) {
|
|
|
|
|
return Cache::get('license_status');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$license = License::query()->latest()->first();
|
|
|
|
|
|
|
|
|
|
if (!$license) {
|
|
|
|
|
return LicenseStatus::noLicense();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
2024-03-22 13:40:52 +00:00
|
|
|
|
$result = $this->connector->send(new ValidateLicenseRequest($license))->object();
|
|
|
|
|
$updatedLicense = $this->updateOrCreateLicenseFromApiResponseBody($result);
|
2024-01-05 16:42:50 +00:00
|
|
|
|
|
|
|
|
|
return self::cacheStatus(LicenseStatus::valid($updatedLicense));
|
2024-03-22 13:40:52 +00:00
|
|
|
|
} catch (RequestException $e) {
|
|
|
|
|
if ($e->getStatus() === Response::HTTP_BAD_REQUEST || $e->getStatus() === Response::HTTP_NOT_FOUND) {
|
2024-01-05 16:42:50 +00:00
|
|
|
|
return self::cacheStatus(LicenseStatus::invalid($license));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw $e;
|
|
|
|
|
} catch (DecryptException) {
|
|
|
|
|
// the license key has been tampered with somehow
|
|
|
|
|
return self::cacheStatus(LicenseStatus::invalid($license));
|
|
|
|
|
} catch (Throwable $e) {
|
|
|
|
|
Log::error($e);
|
|
|
|
|
|
|
|
|
|
return LicenseStatus::unknown($license);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @noinspection PhpIncompatibleReturnTypeInspection */
|
2024-03-22 13:40:52 +00:00
|
|
|
|
private function updateOrCreateLicenseFromApiResponseBody(object $body): License
|
2024-01-05 16:42:50 +00:00
|
|
|
|
{
|
|
|
|
|
return License::query()->updateOrCreate([
|
2024-03-22 13:40:52 +00:00
|
|
|
|
'hash' => sha1($body->license_key->key . $this->hashSalt),
|
2024-01-05 16:42:50 +00:00
|
|
|
|
], [
|
2024-03-22 13:40:52 +00:00
|
|
|
|
'key' => $body->license_key->key,
|
|
|
|
|
'instance' => LicenseInstance::fromJsonObject($body->instance),
|
|
|
|
|
'meta' => LicenseMeta::fromJsonObject($body->meta),
|
|
|
|
|
'created_at' => $body->license_key->created_at,
|
|
|
|
|
'expires_at' => $body->license_key->expires_at,
|
2024-01-05 16:42:50 +00:00
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-11 16:52:55 +00:00
|
|
|
|
private static function deleteLicense(License $license): void
|
|
|
|
|
{
|
|
|
|
|
$license->delete();
|
|
|
|
|
Cache::delete('license_status');
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-05 16:42:50 +00:00
|
|
|
|
private static function cacheStatus(LicenseStatus $status): LicenseStatus
|
|
|
|
|
{
|
|
|
|
|
Cache::put('license_status', $status, now()->addWeek());
|
|
|
|
|
|
|
|
|
|
return $status;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-03 17:02:18 +00:00
|
|
|
|
public function isPlus(): bool
|
|
|
|
|
{
|
2024-01-08 13:02:26 +00:00
|
|
|
|
return $this->getStatus()->isValid();
|
2024-01-03 17:02:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function isCommunity(): bool
|
|
|
|
|
{
|
|
|
|
|
return !$this->isPlus();
|
|
|
|
|
}
|
|
|
|
|
}
|