feat: subscriptions
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions;
|
||||
|
||||
use App\Enums\IdentityVerificationStatus;
|
||||
use App\Models\IdentityVerificationRequest;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class ReviewIdentityVerification
|
||||
{
|
||||
public function execute(
|
||||
IdentityVerificationRequest $verificationRequest,
|
||||
IdentityVerificationStatus $status,
|
||||
User $reviewer,
|
||||
?string $rejectionReason = null,
|
||||
): IdentityVerificationRequest {
|
||||
abort_unless($reviewer->isAdmin(), 403);
|
||||
abort_unless(
|
||||
in_array($status, [IdentityVerificationStatus::APPROVED, IdentityVerificationStatus::REJECTED], true),
|
||||
422,
|
||||
);
|
||||
abort_if(
|
||||
$status === IdentityVerificationStatus::REJECTED && blank($rejectionReason),
|
||||
422,
|
||||
__('api.certification.rejection_reason_required'),
|
||||
);
|
||||
|
||||
$reviewedRequest = DB::transaction(function () use (
|
||||
$rejectionReason,
|
||||
$reviewer,
|
||||
$status,
|
||||
$verificationRequest,
|
||||
): IdentityVerificationRequest {
|
||||
$lockedRequest = IdentityVerificationRequest::query()
|
||||
->whereKey($verificationRequest->getKey())
|
||||
->lockForUpdate()
|
||||
->firstOrFail();
|
||||
|
||||
abort_unless($lockedRequest->status === IdentityVerificationStatus::PENDING, 409);
|
||||
|
||||
$lockedRequest->update([
|
||||
'status' => $status,
|
||||
'reviewed_at' => now(),
|
||||
'reviewed_by' => $reviewer->getKey(),
|
||||
'rejection_reason' => $status === IdentityVerificationStatus::REJECTED
|
||||
? trim((string) $rejectionReason)
|
||||
: null,
|
||||
]);
|
||||
|
||||
if ($status === IdentityVerificationStatus::APPROVED) {
|
||||
$lockedRequest->user()->update([
|
||||
'account_verified_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $lockedRequest->fresh(['user', 'reviewer']);
|
||||
});
|
||||
|
||||
$this->deleteReviewedDocument($reviewedRequest);
|
||||
|
||||
return $reviewedRequest->fresh(['user', 'reviewer']);
|
||||
}
|
||||
|
||||
private function deleteReviewedDocument(IdentityVerificationRequest $verificationRequest): void
|
||||
{
|
||||
if (! $verificationRequest->hasDocument()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Storage::disk($verificationRequest->document_disk)
|
||||
->delete($verificationRequest->document_path);
|
||||
|
||||
$verificationRequest->update([
|
||||
'document_path' => null,
|
||||
'document_deleted_at' => now(),
|
||||
]);
|
||||
} catch (\Throwable $exception) {
|
||||
report($exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions;
|
||||
|
||||
use App\Enums\IdentityVerificationStatus;
|
||||
use App\Models\IdentityVerificationRequest;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
|
||||
use Throwable;
|
||||
|
||||
class SubmitIdentityVerification
|
||||
{
|
||||
public function execute(User $user, UploadedFile $document): IdentityVerificationRequest
|
||||
{
|
||||
return Cache::lock('identity-verification-submit:'.$user->getKey(), 30)
|
||||
->block(5, fn (): IdentityVerificationRequest => $this->store($user, $document));
|
||||
}
|
||||
|
||||
private function store(User $user, UploadedFile $document): IdentityVerificationRequest
|
||||
{
|
||||
$existingRequest = $user->identityVerificationRequest()->first();
|
||||
|
||||
if ($existingRequest?->status === IdentityVerificationStatus::PENDING) {
|
||||
throw new ConflictHttpException(__('api.certification.already_pending'));
|
||||
}
|
||||
|
||||
$disk = (string) config('billing.identity_verification.disk', 'identity_documents');
|
||||
$path = null;
|
||||
|
||||
try {
|
||||
$path = Storage::disk($disk)->putFileAs(
|
||||
'submissions',
|
||||
$document,
|
||||
Str::ulid().'.'.$this->extensionFor($document),
|
||||
['visibility' => 'private'],
|
||||
);
|
||||
|
||||
if (! is_string($path) || $path === '') {
|
||||
throw new \RuntimeException('The identity document could not be stored.');
|
||||
}
|
||||
|
||||
$verificationRequest = IdentityVerificationRequest::query()->updateOrCreate(
|
||||
['user_id' => $user->getKey()],
|
||||
[
|
||||
'status' => IdentityVerificationStatus::PENDING,
|
||||
'document_disk' => $disk,
|
||||
'document_path' => $path,
|
||||
'document_mime_type' => $document->getMimeType(),
|
||||
'document_size' => $document->getSize(),
|
||||
'submitted_at' => now(),
|
||||
'reviewed_at' => null,
|
||||
'reviewed_by' => null,
|
||||
'rejection_reason' => null,
|
||||
'document_deleted_at' => null,
|
||||
],
|
||||
);
|
||||
} catch (Throwable $exception) {
|
||||
if ($path) {
|
||||
Storage::disk($disk)->delete($path);
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
if ($existingRequest?->hasDocument() && $existingRequest->document_path !== $path) {
|
||||
Storage::disk($existingRequest->document_disk)->delete($existingRequest->document_path);
|
||||
}
|
||||
|
||||
return $verificationRequest;
|
||||
}
|
||||
|
||||
private function extensionFor(UploadedFile $document): string
|
||||
{
|
||||
return match ($document->getMimeType()) {
|
||||
'image/jpeg' => 'jpg',
|
||||
'image/png' => 'png',
|
||||
'application/pdf' => 'pdf',
|
||||
default => throw new \InvalidArgumentException('Unsupported identity document type.'),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user