New updates
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\License;
|
||||
use App\Models\LicenseActivation;
|
||||
use App\Services\Licensing\LicenseKey;
|
||||
use App\Services\Licensing\LicenseSigner;
|
||||
use App\Services\Licensing\LicenseTokenVerifier;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
final class LicenseActivationController extends Controller
|
||||
{
|
||||
public function activate(Request $request, LicenseSigner $signer): JsonResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'licenseKey'=>['required','string','max:128'],
|
||||
'installationID'=>['required','uuid'],
|
||||
'appVersion'=>['required','string','max:32'],
|
||||
'platform'=>['required','string','max:32'],
|
||||
]);
|
||||
|
||||
return DB::transaction(function () use ($data,$signer) {
|
||||
$license = License::where('license_key_hash', LicenseKey::hash($data['licenseKey']))->lockForUpdate()->first();
|
||||
if (!$license) throw ValidationException::withMessages(['licenseKey'=>'The licence key is not valid.']);
|
||||
if (!$license->is_usable) return response()->json(['message'=>'This licence is expired or has been revoked.'], 403);
|
||||
|
||||
$activation = LicenseActivation::where('license_id',$license->id)->where('installation_id',strtolower($data['installationID']))->first();
|
||||
if ($activation && $activation->deactivated_at === null) {
|
||||
$activation->update(['last_seen_at'=>now(),'platform'=>$data['platform'],'app_version'=>$data['appVersion']]);
|
||||
} else {
|
||||
$activeCount = LicenseActivation::where('license_id',$license->id)->whereNull('deactivated_at')->count();
|
||||
if ($activeCount >= $license->activation_limit) return response()->json(['message'=>'This licence has reached its activation limit. Deactivate another Mac first.'], 409);
|
||||
if ($activation) $activation->update(['deactivated_at'=>null,'activated_at'=>now(),'last_seen_at'=>now(),'platform'=>$data['platform'],'app_version'=>$data['appVersion']]);
|
||||
else LicenseActivation::create(['license_id'=>$license->id,'installation_id'=>strtolower($data['installationID']),'platform'=>$data['platform'],'app_version'=>$data['appVersion'],'activated_at'=>now(),'last_seen_at'=>now()]);
|
||||
}
|
||||
return response()->json(['licenseToken'=>$signer->sign($license, $data['installationID'])]);
|
||||
});
|
||||
}
|
||||
|
||||
public function deactivate(Request $request, LicenseTokenVerifier $verifier): JsonResponse
|
||||
{
|
||||
$data = $request->validate(['licenseToken'=>['required','string','max:8192'],'installationID'=>['required','uuid']]);
|
||||
try { $payload = $verifier->verify($data['licenseToken']); }
|
||||
catch (\Throwable) { return response()->json(['message'=>'The licence token is invalid.'], 400); }
|
||||
if (strcasecmp((string)($payload['installationID'] ?? ''), $data['installationID']) !== 0) return response()->json(['message'=>'The installation does not match this licence token.'], 400);
|
||||
LicenseActivation::where('license_id',(string)($payload['licenseID'] ?? ''))->where('installation_id',strtolower($data['installationID']))->whereNull('deactivated_at')->update(['deactivated_at'=>now(),'last_seen_at'=>now()]);
|
||||
return response()->json((object)[]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\LicenseWebhookEvent;
|
||||
use App\Services\Payments\LemonSqueezyWebhookVerifier;
|
||||
use App\Services\Payments\PaddleWebhookVerifier;
|
||||
use App\Services\Payments\PurchaseProvisioner;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
final class PaymentWebhookController extends Controller
|
||||
{
|
||||
public function __invoke(string $provider, Request $request, PurchaseProvisioner $provisioner): JsonResponse
|
||||
{
|
||||
$raw=(string)$request->getContent();
|
||||
$verifier=match($provider){'paddle'=>app(PaddleWebhookVerifier::class),'lemon-squeezy'=>app(LemonSqueezyWebhookVerifier::class),default=>null};
|
||||
if (!$verifier) return response()->json(['message'=>'Unknown webhook provider.'],404);
|
||||
try { $verifier->verify($request,$raw); } catch (\Throwable $e) { report($e); return response()->json(['message'=>'Invalid webhook signature.'],400); }
|
||||
try { $payload=json_decode($raw,true,flags:JSON_THROW_ON_ERROR); } catch (\Throwable) { return response()->json(['message'=>'Invalid JSON.'],400); }
|
||||
$externalID=(string)($provider==='paddle'?($payload['event_id']??''):($payload['meta']['event_name']??'').':'.($payload['data']['id']??''));
|
||||
$eventType=(string)($provider==='paddle'?($payload['event_type']??'unknown'):($payload['meta']['event_name']??'unknown'));
|
||||
if ($externalID==='') return response()->json(['message'=>'Webhook event has no stable identifier.'],400);
|
||||
|
||||
try {
|
||||
DB::transaction(function() use($provider,$externalID,$eventType,$payload,$raw,$provisioner){
|
||||
$event=LicenseWebhookEvent::firstOrCreate(['provider'=>$provider,'external_event_id'=>$externalID],['event_type'=>$eventType,'payload_sha256'=>hash('sha256',$raw),'payload'=>$payload]);
|
||||
if ($event->processed_at) return;
|
||||
if (!hash_equals($event->payload_sha256,hash('sha256',$raw))) throw new \RuntimeException('Webhook ID was reused with a different payload.');
|
||||
try { $provisioner->provision($provider,$payload); $event->update(['processed_at'=>now(),'processing_error'=>null]); }
|
||||
catch (\Throwable $e) { $event->update(['processing_error'=>mb_substr($e->getMessage(),0,4000)]); throw $e; }
|
||||
});
|
||||
} catch (\Throwable $e) { report($e); return response()->json(['message'=>'Webhook could not be processed.'],500); }
|
||||
return response()->json(['ok'=>true]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user