mirror of
https://github.com/koel/koel
synced 2024-11-24 05:03:05 +00:00
feat: use Saloon for LemonSqueezy integration
This commit is contained in:
parent
06dfe8a1db
commit
1e0637b0bf
7 changed files with 151 additions and 47 deletions
|
@ -3,20 +3,18 @@
|
||||||
namespace App\Exceptions;
|
namespace App\Exceptions;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use GuzzleHttp\Exception\ClientException;
|
use Saloon\Exceptions\Request\RequestException;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
class FailedToActivateLicenseException extends Exception
|
final class FailedToActivateLicenseException extends Exception
|
||||||
{
|
{
|
||||||
public static function fromThrowable(Throwable $e): self
|
public static function fromThrowable(Throwable $e): self
|
||||||
{
|
{
|
||||||
return new static($e->getMessage(), $e->getCode(), $e);
|
return new self($e->getMessage(), $e->getCode(), $e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function fromClientException(ClientException $e): self
|
public static function fromRequestException(RequestException $e): self
|
||||||
{
|
{
|
||||||
$response = $e->getResponse();
|
return new self(object_get($e->getResponse()->object(), 'error'), $e->getStatus());
|
||||||
|
|
||||||
return new static(json_decode($response->getBody())->error, $response->getStatusCode());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
18
app/Http/Integrations/LemonSqueezy/LemonSqueezyConnector.php
Normal file
18
app/Http/Integrations/LemonSqueezy/LemonSqueezyConnector.php
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Integrations\LemonSqueezy;
|
||||||
|
|
||||||
|
use Saloon\Http\Connector;
|
||||||
|
use Saloon\Traits\Plugins\AcceptsJson;
|
||||||
|
use Saloon\Traits\Plugins\AlwaysThrowOnErrors;
|
||||||
|
|
||||||
|
class LemonSqueezyConnector extends Connector
|
||||||
|
{
|
||||||
|
use AcceptsJson;
|
||||||
|
use AlwaysThrowOnErrors;
|
||||||
|
|
||||||
|
public function resolveBaseUrl(): string
|
||||||
|
{
|
||||||
|
return 'https://api.lemonsqueezy.com/v1/';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Integrations\LemonSqueezy\Requests;
|
||||||
|
|
||||||
|
use Saloon\Contracts\Body\HasBody;
|
||||||
|
use Saloon\Enums\Method;
|
||||||
|
use Saloon\Http\Request;
|
||||||
|
use Saloon\Traits\Body\HasFormBody;
|
||||||
|
|
||||||
|
class ActivateLicenseRequest extends Request implements HasBody
|
||||||
|
{
|
||||||
|
use HasFormBody;
|
||||||
|
|
||||||
|
protected Method $method = Method::POST;
|
||||||
|
|
||||||
|
public function __construct(private string $key)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resolveEndpoint(): string
|
||||||
|
{
|
||||||
|
return '/licenses/activate';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<mixed> */
|
||||||
|
protected function defaultBody(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'license_key' => $this->key,
|
||||||
|
'instance_name' => 'Koel Plus',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Integrations\LemonSqueezy\Requests;
|
||||||
|
|
||||||
|
use App\Models\License;
|
||||||
|
use Saloon\Contracts\Body\HasBody;
|
||||||
|
use Saloon\Enums\Method;
|
||||||
|
use Saloon\Http\Request;
|
||||||
|
use Saloon\Traits\Body\HasFormBody;
|
||||||
|
|
||||||
|
class DeactivateLicenseRequest extends Request implements HasBody
|
||||||
|
{
|
||||||
|
use HasFormBody;
|
||||||
|
|
||||||
|
protected Method $method = Method::POST;
|
||||||
|
|
||||||
|
public function __construct(private License $license)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resolveEndpoint(): string
|
||||||
|
{
|
||||||
|
return '/licenses/deactivate';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<mixed> */
|
||||||
|
protected function defaultBody(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'license_key' => $this->license->key,
|
||||||
|
'instance_id' => $this->license->instance->id,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Integrations\LemonSqueezy\Requests;
|
||||||
|
|
||||||
|
use App\Models\License;
|
||||||
|
use Saloon\Contracts\Body\HasBody;
|
||||||
|
use Saloon\Enums\Method;
|
||||||
|
use Saloon\Http\Request;
|
||||||
|
use Saloon\Traits\Body\HasFormBody;
|
||||||
|
|
||||||
|
class ValidateLicenseRequest extends Request implements HasBody
|
||||||
|
{
|
||||||
|
use HasFormBody;
|
||||||
|
|
||||||
|
protected Method $method = Method::POST;
|
||||||
|
|
||||||
|
public function __construct(private License $license)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resolveEndpoint(): string
|
||||||
|
{
|
||||||
|
return '/licenses/validate';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<mixed> */
|
||||||
|
protected function defaultBody(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'license_key' => $this->license->key,
|
||||||
|
'instance_id' => $this->license->instance->id,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
use App\Services\ApiClients\ApiClient;
|
|
||||||
use App\Services\ApiClients\LemonSqueezyApiClient;
|
|
||||||
use App\Services\Contracts\MusicEncyclopedia;
|
use App\Services\Contracts\MusicEncyclopedia;
|
||||||
use App\Services\LastfmService;
|
use App\Services\LastfmService;
|
||||||
use App\Services\License\Contracts\LicenseServiceInterface;
|
use App\Services\License\Contracts\LicenseServiceInterface;
|
||||||
|
@ -47,7 +45,6 @@ class AppServiceProvider extends ServiceProvider
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->app->bind(LicenseServiceInterface::class, LicenseService::class);
|
$this->app->bind(LicenseServiceInterface::class, LicenseService::class);
|
||||||
$this->app->bind(ApiClient::class, LemonSqueezyApiClient::class);
|
|
||||||
|
|
||||||
$this->app->when(LicenseService::class)
|
$this->app->when(LicenseService::class)
|
||||||
->needs('$hashSalt')
|
->needs('$hashSalt')
|
||||||
|
|
|
@ -3,43 +3,43 @@
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
use App\Exceptions\FailedToActivateLicenseException;
|
use App\Exceptions\FailedToActivateLicenseException;
|
||||||
|
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;
|
||||||
use App\Models\License;
|
use App\Models\License;
|
||||||
use App\Services\ApiClients\ApiClient;
|
|
||||||
use App\Services\License\Contracts\LicenseServiceInterface;
|
use App\Services\License\Contracts\LicenseServiceInterface;
|
||||||
use App\Values\LicenseInstance;
|
use App\Values\LicenseInstance;
|
||||||
use App\Values\LicenseMeta;
|
use App\Values\LicenseMeta;
|
||||||
use App\Values\LicenseStatus;
|
use App\Values\LicenseStatus;
|
||||||
use GuzzleHttp\Exception\ClientException;
|
|
||||||
use Illuminate\Contracts\Encryption\DecryptException;
|
use Illuminate\Contracts\Encryption\DecryptException;
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Saloon\Exceptions\Request\RequestException;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
class LicenseService implements LicenseServiceInterface
|
class LicenseService implements LicenseServiceInterface
|
||||||
{
|
{
|
||||||
public function __construct(private ApiClient $client, private string $hashSalt)
|
public function __construct(private LemonSqueezyConnector $connector, private string $hashSalt)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function activate(string $key): License
|
public function activate(string $key): License
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$response = $this->client->post('licenses/activate', [
|
$result = $this->connector->send(new ActivateLicenseRequest($key))->object();
|
||||||
'license_key' => $key,
|
|
||||||
'instance_name' => 'Koel Plus',
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($response->meta->store_id !== config('lemonsqueezy.store_id')) {
|
if ($result->meta->store_id !== config('lemonsqueezy.store_id')) {
|
||||||
throw new FailedToActivateLicenseException('This license key is not from Koel’s official store.');
|
throw new FailedToActivateLicenseException('This license key is not from Koel’s official store.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$license = $this->updateOrCreateLicenseFromApiResponse($response);
|
$license = $this->updateOrCreateLicenseFromApiResponseBody($result);
|
||||||
$this->cacheStatus(LicenseStatus::valid($license));
|
$this->cacheStatus(LicenseStatus::valid($license));
|
||||||
|
|
||||||
return $license;
|
return $license;
|
||||||
} catch (ClientException $e) {
|
} catch (RequestException $e) {
|
||||||
throw FailedToActivateLicenseException::fromClientException($e);
|
throw FailedToActivateLicenseException::fromRequestException($e);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
Log::error($e);
|
Log::error($e);
|
||||||
throw FailedToActivateLicenseException::fromThrowable($e);
|
throw FailedToActivateLicenseException::fromThrowable($e);
|
||||||
|
@ -49,23 +49,20 @@ class LicenseService implements LicenseServiceInterface
|
||||||
public function deactivate(License $license): void
|
public function deactivate(License $license): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$response = $this->client->post('licenses/deactivate', [
|
$result = $this->connector->send(new DeactivateLicenseRequest($license))->object();
|
||||||
'license_key' => $license->key,
|
|
||||||
'instance_id' => $license->instance->id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($response->deactivated) {
|
if ($result->deactivated) {
|
||||||
self::deleteLicense($license);
|
self::deleteLicense($license);
|
||||||
}
|
}
|
||||||
} catch (ClientException $e) {
|
} catch (RequestException $e) {
|
||||||
if ($e->getResponse()->getStatusCode() === Response::HTTP_NOT_FOUND) {
|
if ($e->getStatus() === Response::HTTP_NOT_FOUND) {
|
||||||
// The instance ID was not found. The license record must be a leftover from an erroneous attempt.
|
// The instance ID was not found. The license record must be a leftover from an erroneous attempt.
|
||||||
self::deleteLicense($license);
|
self::deleteLicense($license);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw FailedToActivateLicenseException::fromClientException($e);
|
throw FailedToActivateLicenseException::fromRequestException($e);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
Log::error($e);
|
Log::error($e);
|
||||||
throw $e;
|
throw $e;
|
||||||
|
@ -86,19 +83,12 @@ class LicenseService implements LicenseServiceInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$response = $this->client->post('licenses/validate', [
|
$result = $this->connector->send(new ValidateLicenseRequest($license))->object();
|
||||||
'license_key' => $license->key,
|
$updatedLicense = $this->updateOrCreateLicenseFromApiResponseBody($result);
|
||||||
'instance_id' => $license->instance->id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$updatedLicense = $this->updateOrCreateLicenseFromApiResponse($response);
|
|
||||||
|
|
||||||
return self::cacheStatus(LicenseStatus::valid($updatedLicense));
|
return self::cacheStatus(LicenseStatus::valid($updatedLicense));
|
||||||
} catch (ClientException $e) {
|
} catch (RequestException $e) {
|
||||||
Log::error($e);
|
if ($e->getStatus() === Response::HTTP_BAD_REQUEST || $e->getStatus() === Response::HTTP_NOT_FOUND) {
|
||||||
$statusCode = $e->getResponse()->getStatusCode();
|
|
||||||
|
|
||||||
if ($statusCode === Response::HTTP_BAD_REQUEST || $statusCode === Response::HTTP_NOT_FOUND) {
|
|
||||||
return self::cacheStatus(LicenseStatus::invalid($license));
|
return self::cacheStatus(LicenseStatus::invalid($license));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,16 +104,16 @@ class LicenseService implements LicenseServiceInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @noinspection PhpIncompatibleReturnTypeInspection */
|
/** @noinspection PhpIncompatibleReturnTypeInspection */
|
||||||
private function updateOrCreateLicenseFromApiResponse(object $response): License
|
private function updateOrCreateLicenseFromApiResponseBody(object $body): License
|
||||||
{
|
{
|
||||||
return License::query()->updateOrCreate([
|
return License::query()->updateOrCreate([
|
||||||
'hash' => sha1($response->license_key->key . $this->hashSalt),
|
'hash' => sha1($body->license_key->key . $this->hashSalt),
|
||||||
], [
|
], [
|
||||||
'key' => $response->license_key->key,
|
'key' => $body->license_key->key,
|
||||||
'instance' => LicenseInstance::fromJsonObject($response->instance),
|
'instance' => LicenseInstance::fromJsonObject($body->instance),
|
||||||
'meta' => LicenseMeta::fromJsonObject($response->meta),
|
'meta' => LicenseMeta::fromJsonObject($body->meta),
|
||||||
'created_at' => $response->license_key->created_at,
|
'created_at' => $body->license_key->created_at,
|
||||||
'expires_at' => $response->license_key->expires_at,
|
'expires_at' => $body->license_key->expires_at,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue