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
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Maatify\Verification\Application\Exceptions;

use Maatify\Exceptions\Contracts\ErrorCategoryInterface;
use Maatify\Exceptions\Enum\ErrorCategoryEnum;

class VerificationAttemptsExceededException extends VerificationException
{
protected string $errorCode = 'VERIFICATION_ATTEMPTS_EXCEEDED';
protected string $messageKey = 'verification.attempts_exceeded';
protected int $statusCode = 429;
protected ErrorCategoryInterface $category = ErrorCategoryEnum::RATE_LIMIT;
}
21 changes: 21 additions & 0 deletions src/Application/Exceptions/VerificationErrorCodeEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Maatify\Verification\Application\Exceptions;

use Maatify\Exceptions\Contracts\ErrorCodeInterface;

enum VerificationErrorCodeEnum: string implements ErrorCodeInterface
{
case VERIFICATION_INVALID_CODE = 'VERIFICATION_INVALID_CODE';
case VERIFICATION_EXPIRED = 'VERIFICATION_EXPIRED';
case VERIFICATION_ATTEMPTS_EXCEEDED = 'VERIFICATION_ATTEMPTS_EXCEEDED';
case VERIFICATION_GENERATION_BLOCKED = 'VERIFICATION_GENERATION_BLOCKED';
case VERIFICATION_INTERNAL = 'VERIFICATION_INTERNAL';

public function getValue(): string
{
return $this->value;
}
}
66 changes: 66 additions & 0 deletions src/Application/Exceptions/VerificationException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

declare(strict_types=1);

namespace Maatify\Verification\Application\Exceptions;

use Maatify\Exceptions\Contracts\ErrorCategoryInterface;
use Maatify\Exceptions\Contracts\ErrorCodeInterface;
use Maatify\Exceptions\Contracts\ErrorPolicyInterface;
use Maatify\Exceptions\Contracts\EscalationPolicyInterface;
use Maatify\Exceptions\Enum\ErrorCategoryEnum;
use Maatify\Exceptions\Exception\MaatifyException;
use Throwable;

abstract class VerificationException extends MaatifyException
{
protected string $errorCode = 'VERIFICATION_INTERNAL';
protected string $messageKey = 'verification.internal_error';
protected int $statusCode = 500;
protected ErrorCategoryInterface $category = ErrorCategoryEnum::SYSTEM;

public function __construct(
string $message = '',
int $code = 0,
?Throwable $previous = null,
?ErrorCodeInterface $errorCodeOverride = null,
?int $httpStatusOverride = null,
?bool $isSafeOverride = null,
?bool $isRetryableOverride = null,
array $meta = [],
?ErrorPolicyInterface $policy = null,
?EscalationPolicyInterface $escalationPolicy = null
) {
if ($message === '') {
$message = $this->messageKey;
}

parent::__construct(
$message,
$code,
$previous,
$errorCodeOverride,
$httpStatusOverride,
$isSafeOverride,
$isRetryableOverride,
$meta,
$policy,
$escalationPolicy
);
}

protected function defaultErrorCode(): ErrorCodeInterface
{
return VerificationErrorCodeEnum::from($this->errorCode);
}

protected function defaultCategory(): ErrorCategoryInterface
{
return $this->category;
}

protected function defaultHttpStatus(): int
{
return $this->statusCode;
}
}
16 changes: 16 additions & 0 deletions src/Application/Exceptions/VerificationExpiredException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Maatify\Verification\Application\Exceptions;

use Maatify\Exceptions\Contracts\ErrorCategoryInterface;
use Maatify\Exceptions\Enum\ErrorCategoryEnum;

class VerificationExpiredException extends VerificationException
{
protected string $errorCode = 'VERIFICATION_EXPIRED';
protected string $messageKey = 'verification.expired';
protected int $statusCode = 410;
protected ErrorCategoryInterface $category = ErrorCategoryEnum::BUSINESS_RULE;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Maatify\Verification\Application\Exceptions;

use Maatify\Exceptions\Contracts\ErrorCategoryInterface;
use Maatify\Exceptions\Enum\ErrorCategoryEnum;

class VerificationGenerationBlockedException extends VerificationException
{
protected string $errorCode = 'VERIFICATION_GENERATION_BLOCKED';
protected string $messageKey = 'verification.generation_blocked';
protected int $statusCode = 429;
protected ErrorCategoryInterface $category = ErrorCategoryEnum::RATE_LIMIT;
}
16 changes: 16 additions & 0 deletions src/Application/Exceptions/VerificationInternalException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Maatify\Verification\Application\Exceptions;

use Maatify\Exceptions\Contracts\ErrorCategoryInterface;
use Maatify\Exceptions\Enum\ErrorCategoryEnum;

class VerificationInternalException extends VerificationException
{
protected string $errorCode = 'VERIFICATION_INTERNAL';
protected string $messageKey = 'verification.internal_error';
protected int $statusCode = 500;
protected ErrorCategoryInterface $category = ErrorCategoryEnum::SYSTEM;
}
16 changes: 16 additions & 0 deletions src/Application/Exceptions/VerificationInvalidCodeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Maatify\Verification\Application\Exceptions;

use Maatify\Exceptions\Contracts\ErrorCategoryInterface;
use Maatify\Exceptions\Enum\ErrorCategoryEnum;

class VerificationInvalidCodeException extends VerificationException
{
protected string $errorCode = 'VERIFICATION_INVALID_CODE';
protected string $messageKey = 'verification.invalid_code';
protected int $statusCode = 422;
protected ErrorCategoryInterface $category = ErrorCategoryEnum::VALIDATION;
}
85 changes: 85 additions & 0 deletions src/Application/Verification/VerificationService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

declare(strict_types=1);

namespace Maatify\Verification\Application\Verification;

use Maatify\Verification\Application\Exceptions\VerificationAttemptsExceededException;
use Maatify\Verification\Application\Exceptions\VerificationExpiredException;
use Maatify\Verification\Application\Exceptions\VerificationGenerationBlockedException;
use Maatify\Verification\Application\Exceptions\VerificationInternalException;
use Maatify\Verification\Application\Exceptions\VerificationInvalidCodeException;
use Maatify\Verification\Domain\Contracts\VerificationCodeGeneratorInterface;
use Maatify\Verification\Domain\Contracts\VerificationCodeValidatorInterface;
use Maatify\Verification\Domain\Enum\IdentityTypeEnum;
use Maatify\Verification\Domain\Enum\VerificationFailureEnum;
use Maatify\Verification\Domain\Enum\VerificationPurposeEnum;
use Maatify\Verification\Domain\Exception\VerificationGenerationRateLimitedException;
use Throwable;

readonly class VerificationService implements VerificationServiceInterface
{
public function __construct(
private VerificationCodeGeneratorInterface $generator,
private VerificationCodeValidatorInterface $validator
) {
}

public function startVerification(
IdentityTypeEnum $identityType,
string $identity,
VerificationPurposeEnum $purpose
): string {
try {
$generated = $this->generator->generate($identityType, $identity, $purpose);

return $generated->plainCode;
} catch (VerificationGenerationRateLimitedException $e) {
throw new VerificationGenerationBlockedException($e->getMessage(), 0, $e);
} catch (Throwable $e) {
throw new VerificationInternalException('An unexpected verification error occurred.', 0, $e);
}
}

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

if (! $result->success) {
return match ($result->failureCode) {
VerificationFailureEnum::INVALID_CODE => throw new VerificationInvalidCodeException($result->reason),
VerificationFailureEnum::EXPIRED => throw new VerificationExpiredException($result->reason),
VerificationFailureEnum::ATTEMPTS_EXCEEDED => throw new VerificationAttemptsExceededException($result->reason),
default => throw new VerificationInternalException($result->reason),
};
}

return true;
} catch (VerificationInvalidCodeException | VerificationExpiredException | VerificationAttemptsExceededException $e) {
throw $e;
} catch (Throwable $e) {
throw new VerificationInternalException('An unexpected verification error occurred.', 0, $e);
}
}

public function resendVerification(
IdentityTypeEnum $identityType,
string $identity,
VerificationPurposeEnum $purpose
): string {
try {
$generated = $this->generator->generate($identityType, $identity, $purpose);

return $generated->plainCode;
} catch (VerificationGenerationRateLimitedException $e) {
throw new VerificationGenerationBlockedException($e->getMessage(), 0, $e);
} catch (Throwable $e) {
throw new VerificationInternalException('An unexpected verification error occurred.', 0, $e);
}
}
}
55 changes: 55 additions & 0 deletions src/Application/Verification/VerificationServiceInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace Maatify\Verification\Application\Verification;

use Maatify\Verification\Domain\Enum\IdentityTypeEnum;
use Maatify\Verification\Domain\Enum\VerificationPurposeEnum;

interface VerificationServiceInterface
{
/**
* Starts a new verification process by generating a code.
*
* @param IdentityTypeEnum $identityType
* @param string $identity
* @param VerificationPurposeEnum $purpose
* @return string The plain generated verification code.
*/
public function startVerification(
IdentityTypeEnum $identityType,
string $identity,
VerificationPurposeEnum $purpose
): string;

/**
* Verifies an existing code.
*
* @param IdentityTypeEnum $identityType
* @param string $identity
* @param VerificationPurposeEnum $purpose
* @param string $code
* @return bool True if verification succeeds, false otherwise.
*/
public function verifyCode(
IdentityTypeEnum $identityType,
string $identity,
VerificationPurposeEnum $purpose,
string $code
): bool;

/**
* Resends a verification code.
*
* @param IdentityTypeEnum $identityType
* @param string $identity
* @param VerificationPurposeEnum $purpose
* @return string The plain generated verification code.
*/
public function resendVerification(
IdentityTypeEnum $identityType,
string $identity,
VerificationPurposeEnum $purpose
): string;
}
10 changes: 6 additions & 4 deletions src/Domain/DTO/VerificationResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Maatify\Verification\Domain\DTO;

use Maatify\Verification\Domain\Enum\IdentityTypeEnum;
use Maatify\Verification\Domain\Enum\VerificationFailureEnum;
use Maatify\Verification\Domain\Enum\VerificationPurposeEnum;

readonly class VerificationResult
Expand All @@ -14,17 +15,18 @@ public function __construct(
public string $reason = '',
public ?IdentityTypeEnum $identityType = null,
public ?string $identityId = null,
public ?VerificationPurposeEnum $purpose = null
public ?VerificationPurposeEnum $purpose = null,
public ?VerificationFailureEnum $failureCode = null
) {
}

public static function success(?IdentityTypeEnum $identityType = null, ?string $identityId = null, ?VerificationPurposeEnum $purpose = null): self
{
return new self(true, '', $identityType, $identityId, $purpose);
return new self(true, '', $identityType, $identityId, $purpose, null);
}

public static function failure(string $reason): self
public static function failure(VerificationFailureEnum $failureCode, string $reason): self
{
return new self(false, $reason);
return new self(false, $reason, null, null, null, $failureCode);
}
}
12 changes: 12 additions & 0 deletions src/Domain/Enum/VerificationFailureEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Maatify\Verification\Domain\Enum;

enum VerificationFailureEnum: string
{
case INVALID_CODE = 'INVALID_CODE';
case EXPIRED = 'EXPIRED';
case ATTEMPTS_EXCEEDED = 'ATTEMPTS_EXCEEDED';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Maatify\Verification\Domain\Exception;

use RuntimeException;

class VerificationGenerationRateLimitedException extends RuntimeException
{
}
Loading