Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@

"require": {
"php": "^8.2",
"ext-pdo": "*",
"ext-json": "*",
"ext-pdo": "*",
"ext-redis": "*",

"maatify/exceptions": "^1.1",
"maatify/shared-common": "^1.0"
},

Expand Down
22 changes: 22 additions & 0 deletions src/Application/Enum/VerificationErrorCodeEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Maatify\Verification\Application\Enum;

use Maatify\Exceptions\Contracts\ErrorCodeInterface;

enum VerificationErrorCodeEnum: string implements ErrorCodeInterface
{
case INVALID_CODE = 'INVALID_CODE';
case EXPIRED_CODE = 'EXPIRED_CODE';
case ATTEMPTS_EXCEEDED = 'ATTEMPTS_EXCEEDED';
case GENERATION_BLOCKED = 'GENERATION_BLOCKED';
case RATE_LIMIT = 'RATE_LIMIT';
case INTERNAL_ERROR = 'INTERNAL_ERROR';

public function getValue(): string
{
return $this->value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Maatify\Verification\Application\Exception;

use Maatify\Exceptions\Contracts\ErrorCategoryInterface;
use Maatify\Exceptions\Contracts\ErrorCodeInterface;
use Maatify\Exceptions\Enum\ErrorCategoryEnum;
use Maatify\Verification\Application\Enum\VerificationErrorCodeEnum;

class VerificationAttemptsExceededException extends VerificationException
{
public function __construct(string $message = 'Maximum verification attempts exceeded.')
{
parent::__construct($message);
}

protected function defaultErrorCode(): ErrorCodeInterface
{
return VerificationErrorCodeEnum::ATTEMPTS_EXCEEDED;
}

protected function defaultCategory(): ErrorCategoryInterface
{
return ErrorCategoryEnum::RATE_LIMIT;
}

protected function defaultHttpStatus(): int
{
return 429;
}
}
33 changes: 33 additions & 0 deletions src/Application/Exception/VerificationCodeExpiredException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Maatify\Verification\Application\Exception;

use Maatify\Exceptions\Contracts\ErrorCategoryInterface;
use Maatify\Exceptions\Contracts\ErrorCodeInterface;
use Maatify\Exceptions\Enum\ErrorCategoryEnum;
use Maatify\Verification\Application\Enum\VerificationErrorCodeEnum;

class VerificationCodeExpiredException extends VerificationException
{
public function __construct(string $message = 'Verification code has expired.')
{
parent::__construct($message);
}

protected function defaultErrorCode(): ErrorCodeInterface
{
return VerificationErrorCodeEnum::EXPIRED_CODE;
}

protected function defaultCategory(): ErrorCategoryInterface
{
return ErrorCategoryEnum::VALIDATION;
}

protected function defaultHttpStatus(): int
{
return 400;
}
}
16 changes: 16 additions & 0 deletions src/Application/Exception/VerificationException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Maatify\Verification\Application\Exception;

use Maatify\Exceptions\Exception\MaatifyException;
use Maatify\Exceptions\Contracts\ErrorCategoryInterface;
use Maatify\Exceptions\Contracts\ErrorCodeInterface;

abstract class VerificationException extends MaatifyException
{
abstract protected function defaultErrorCode(): ErrorCodeInterface;
abstract protected function defaultCategory(): ErrorCategoryInterface;
abstract protected function defaultHttpStatus(): int;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Maatify\Verification\Application\Exception;

use Maatify\Exceptions\Contracts\ErrorCategoryInterface;
use Maatify\Exceptions\Contracts\ErrorCodeInterface;
use Maatify\Exceptions\Enum\ErrorCategoryEnum;
use Maatify\Verification\Application\Enum\VerificationErrorCodeEnum;

class VerificationGenerationBlockedException extends VerificationException
{
public function __construct(string $message = 'Verification code generation is temporarily blocked.')
{
parent::__construct($message);
}

protected function defaultErrorCode(): ErrorCodeInterface
{
return VerificationErrorCodeEnum::GENERATION_BLOCKED;
}

protected function defaultCategory(): ErrorCategoryInterface
{
return ErrorCategoryEnum::RATE_LIMIT;
}

protected function defaultHttpStatus(): int
{
return 429;
}
}
33 changes: 33 additions & 0 deletions src/Application/Exception/VerificationInternalException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Maatify\Verification\Application\Exception;

use Maatify\Exceptions\Contracts\ErrorCategoryInterface;
use Maatify\Exceptions\Contracts\ErrorCodeInterface;
use Maatify\Exceptions\Enum\ErrorCategoryEnum;
use Maatify\Verification\Application\Enum\VerificationErrorCodeEnum;

class VerificationInternalException extends VerificationException
{
public function __construct(string $message = 'An internal error occurred during verification.')
{
parent::__construct($message);
}

protected function defaultErrorCode(): ErrorCodeInterface
{
return VerificationErrorCodeEnum::INTERNAL_ERROR;
}

protected function defaultCategory(): ErrorCategoryInterface
{
return ErrorCategoryEnum::SYSTEM;
}

protected function defaultHttpStatus(): int
{
return 500;
}
}
33 changes: 33 additions & 0 deletions src/Application/Exception/VerificationInvalidCodeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Maatify\Verification\Application\Exception;

use Maatify\Exceptions\Contracts\ErrorCategoryInterface;
use Maatify\Exceptions\Contracts\ErrorCodeInterface;
use Maatify\Exceptions\Enum\ErrorCategoryEnum;
use Maatify\Verification\Application\Enum\VerificationErrorCodeEnum;

class VerificationInvalidCodeException extends VerificationException
{
public function __construct(string $message = 'Invalid verification code.')
{
parent::__construct($message);
}

protected function defaultErrorCode(): ErrorCodeInterface
{
return VerificationErrorCodeEnum::INVALID_CODE;
}

protected function defaultCategory(): ErrorCategoryInterface
{
return ErrorCategoryEnum::VALIDATION;
}

protected function defaultHttpStatus(): int
{
return 400;
}
}
33 changes: 33 additions & 0 deletions src/Application/Exception/VerificationRateLimitException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Maatify\Verification\Application\Exception;

use Maatify\Exceptions\Contracts\ErrorCategoryInterface;
use Maatify\Exceptions\Contracts\ErrorCodeInterface;
use Maatify\Exceptions\Enum\ErrorCategoryEnum;
use Maatify\Verification\Application\Enum\VerificationErrorCodeEnum;

class VerificationRateLimitException extends VerificationException
{
public function __construct(string $message = 'Too many requests. Please try again later.')
{
parent::__construct($message);
}

protected function defaultErrorCode(): ErrorCodeInterface
{
return VerificationErrorCodeEnum::RATE_LIMIT;
}

protected function defaultCategory(): ErrorCategoryInterface
{
return ErrorCategoryEnum::RATE_LIMIT;
}

protected function defaultHttpStatus(): int
{
return 429;
}
}
80 changes: 71 additions & 9 deletions src/Application/Verification/VerificationService.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,17 @@

namespace Maatify\Verification\Application\Verification;

use Maatify\Verification\Application\Exception\VerificationAttemptsExceededException;
use Maatify\Verification\Application\Exception\VerificationCodeExpiredException;
use Maatify\Verification\Application\Exception\VerificationInvalidCodeException;
use Maatify\Verification\Application\Exception\VerificationGenerationBlockedException;
use Maatify\Verification\Application\Exception\VerificationInternalException;
use Maatify\Verification\Application\Exception\VerificationRateLimitException;
use Maatify\Verification\Domain\Contracts\VerificationCodeGeneratorInterface;
use Maatify\Verification\Domain\Contracts\VerificationCodeValidatorInterface;
use Maatify\Verification\Domain\Enum\IdentityTypeEnum;
use Maatify\Verification\Domain\Enum\VerificationPurposeEnum;
use RuntimeException;

readonly class VerificationService implements VerificationServiceInterface
{
Expand All @@ -22,29 +29,84 @@ public function startVerification(
string $identity,
VerificationPurposeEnum $purpose
): string {
$generated = $this->generator->generate($identityType, $identity, $purpose);

return $generated->plainCode;
try {
$generated = $this->generator->generate($identityType, $identity, $purpose);
return $generated->plainCode;
} catch (RuntimeException $e) {
$this->mapGenerationException($e);
}
}

public function verifyCode(
IdentityTypeEnum $identityType,
string $identity,
VerificationPurposeEnum $purpose,
string $code
): bool {
$result = $this->validator->validate($identityType, $identity, $purpose, $code);

return $result->success;
): void {
try {
$result = $this->validator->validate($identityType, $identity, $purpose, $code);
if (!$result->success) {
throw new RuntimeException($result->reason);
}
} catch (RuntimeException $e) {
$this->mapValidationException($e);
}
}

public function resendVerification(
IdentityTypeEnum $identityType,
string $identity,
VerificationPurposeEnum $purpose
): string {
$generated = $this->generator->generate($identityType, $identity, $purpose);
try {
$generated = $this->generator->generate($identityType, $identity, $purpose);
return $generated->plainCode;
} catch (RuntimeException $e) {
$this->mapGenerationException($e);
}
}

/**
* @return never
*/
private function mapGenerationException(RuntimeException $e): void
{
$message = strtolower($e->getMessage());

if (str_contains($message, 'rate limit exceeded')) {
throw new VerificationRateLimitException($e->getMessage());
}

if (str_contains($message, 'too many codes')) {
throw new VerificationRateLimitException($e->getMessage());
}

if (str_contains($message, 'please wait')) {
throw new VerificationGenerationBlockedException($e->getMessage());
}

throw new VerificationInternalException($e->getMessage());
}

/**
* @return never
*/
private function mapValidationException(RuntimeException $e): void
{
$message = strtolower($e->getMessage());

if (str_contains($message, 'expired')) {
throw new VerificationCodeExpiredException($e->getMessage());
}

if (str_contains($message, 'attempts')) {
throw new VerificationAttemptsExceededException($e->getMessage());
}

if (str_contains($message, 'invalid')) {
throw new VerificationInvalidCodeException($e->getMessage());
}

return $generated->plainCode;
throw new VerificationInternalException($e->getMessage());
}
}
Loading