From 08c139426f9d223a7badc7972f2413dd6c8284bb Mon Sep 17 00:00:00 2001 From: Yoan-Alexander Grigorov Date: Thu, 1 Feb 2024 15:55:21 +0100 Subject: [PATCH] :recycle: Refactored publish library to work with separete message classes :sparkles: Introduced AuthorizationDisruption message --- src/Providers/SnsServiceProvider.php | 4 +- src/Publish/AuthorizationDisruption.php | 27 +++++ .../EmptyPayloadException.php | 2 +- src/{Sns => Publish}/FailureCode.php | 2 +- src/{Sns => Publish}/LocalClient.php | 2 +- src/Publish/Message.php | 29 +++++ src/Publish/PaymentFailed.php | 29 +++++ src/Publish/PaymentSuccessful.php | 28 +++++ src/Publish/PublishJob.php | 27 +++++ src/Publish/Publisher.php | 36 ++++++ src/Sns/PublishJob.php | 39 ------ src/Sns/Publisher.php | 48 -------- tests/{Sns => Publish}/PublishJobTest.php | 26 ++-- tests/{Sns => Publish}/PublisherTest.php | 112 +++++++++++++----- 14 files changed, 273 insertions(+), 138 deletions(-) create mode 100644 src/Publish/AuthorizationDisruption.php rename src/{Sns => Publish}/EmptyPayloadException.php (69%) rename src/{Sns => Publish}/FailureCode.php (75%) rename src/{Sns => Publish}/LocalClient.php (91%) create mode 100644 src/Publish/Message.php create mode 100644 src/Publish/PaymentFailed.php create mode 100644 src/Publish/PaymentSuccessful.php create mode 100644 src/Publish/PublishJob.php create mode 100644 src/Publish/Publisher.php delete mode 100644 src/Sns/PublishJob.php delete mode 100644 src/Sns/Publisher.php rename tests/{Sns => Publish}/PublishJobTest.php (56%) rename tests/{Sns => Publish}/PublisherTest.php (59%) diff --git a/src/Providers/SnsServiceProvider.php b/src/Providers/SnsServiceProvider.php index 092364c..c83436e 100644 --- a/src/Providers/SnsServiceProvider.php +++ b/src/Providers/SnsServiceProvider.php @@ -5,8 +5,8 @@ use Aws\Sns\SnsClient; use GuzzleHttp\Client; use Illuminate\Support\ServiceProvider; -use MyParcelCom\Payments\Providers\Sns\LocalClient; -use MyParcelCom\Payments\Providers\Sns\Publisher; +use MyParcelCom\Payments\Providers\Publish\LocalClient; +use MyParcelCom\Payments\Providers\Publish\Publisher; class SnsServiceProvider extends ServiceProvider { diff --git a/src/Publish/AuthorizationDisruption.php b/src/Publish/AuthorizationDisruption.php new file mode 100644 index 0000000..422293b --- /dev/null +++ b/src/Publish/AuthorizationDisruption.php @@ -0,0 +1,27 @@ + $this->shopId, + 'payment_provider_code' => $this->paymentProviderCode, + ]; + } +} diff --git a/src/Sns/EmptyPayloadException.php b/src/Publish/EmptyPayloadException.php similarity index 69% rename from src/Sns/EmptyPayloadException.php rename to src/Publish/EmptyPayloadException.php index 83a9f23..34a3ecb 100644 --- a/src/Sns/EmptyPayloadException.php +++ b/src/Publish/EmptyPayloadException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace MyParcelCom\Payments\Providers\Sns; +namespace MyParcelCom\Payments\Providers\Publish; use RuntimeException; diff --git a/src/Sns/FailureCode.php b/src/Publish/FailureCode.php similarity index 75% rename from src/Sns/FailureCode.php rename to src/Publish/FailureCode.php index f73690d..8cf2c43 100644 --- a/src/Sns/FailureCode.php +++ b/src/Publish/FailureCode.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace MyParcelCom\Payments\Providers\Sns; +namespace MyParcelCom\Payments\Providers\Publish; enum FailureCode: string { diff --git a/src/Sns/LocalClient.php b/src/Publish/LocalClient.php similarity index 91% rename from src/Sns/LocalClient.php rename to src/Publish/LocalClient.php index cc1a1f7..16d4be6 100644 --- a/src/Sns/LocalClient.php +++ b/src/Publish/LocalClient.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace MyParcelCom\Payments\Providers\Sns; +namespace MyParcelCom\Payments\Providers\Publish; use GuzzleHttp\Client; use GuzzleHttp\Promise\PromiseInterface; diff --git a/src/Publish/Message.php b/src/Publish/Message.php new file mode 100644 index 0000000..d2d8c13 --- /dev/null +++ b/src/Publish/Message.php @@ -0,0 +1,29 @@ +topicArn = $topicArn ?? config('publish.sns.topic_arn'); + } + + public function getTopicArn(): string + { + return $this->topicArn; + } + + public function getType(): string + { + return strtolower(Str::snake(class_basename($this))); + } + + abstract public function payload(): array; +} diff --git a/src/Publish/PaymentFailed.php b/src/Publish/PaymentFailed.php new file mode 100644 index 0000000..da3b82e --- /dev/null +++ b/src/Publish/PaymentFailed.php @@ -0,0 +1,29 @@ + $this->myparcelcomPaymentId, + 'failure_code' => $this->failureCode->value, + 'failure_message' => $this->failureMessage, + ]; + } +} diff --git a/src/Publish/PaymentSuccessful.php b/src/Publish/PaymentSuccessful.php new file mode 100644 index 0000000..3033c4b --- /dev/null +++ b/src/Publish/PaymentSuccessful.php @@ -0,0 +1,28 @@ + $this->myparcelcomPaymentId, + 'paid_at' => $this->paidAt->format(DateTimeInterface::ATOM), + ]; + } +} diff --git a/src/Publish/PublishJob.php b/src/Publish/PublishJob.php new file mode 100644 index 0000000..5f92206 --- /dev/null +++ b/src/Publish/PublishJob.php @@ -0,0 +1,27 @@ +publish($this->message) + ->wait(); + } +} diff --git a/src/Publish/Publisher.php b/src/Publish/Publisher.php new file mode 100644 index 0000000..fd8cf7f --- /dev/null +++ b/src/Publish/Publisher.php @@ -0,0 +1,36 @@ + $message->getType(), + 'data' => $message->payload(), + ]; + + if (Env::get('APP_ENV') === 'local') { + return $this->localClient->publish($payload); + } + + return $this->snsClient->publishAsync([ + 'Message' => Utils::jsonEncode($payload), + 'TopicArn' => $message->getTopicArn(), + ]); + } +} diff --git a/src/Sns/PublishJob.php b/src/Sns/PublishJob.php deleted file mode 100644 index 0a10f8a..0000000 --- a/src/Sns/PublishJob.php +++ /dev/null @@ -1,39 +0,0 @@ -publish( - $this->topicArn, - $this->myparcelcomPaymentId, - $this->paidAt, - $this->failureCode, - $this->failureMessage, - ) - ->wait(); - } -} diff --git a/src/Sns/Publisher.php b/src/Sns/Publisher.php deleted file mode 100644 index a2bac28..0000000 --- a/src/Sns/Publisher.php +++ /dev/null @@ -1,48 +0,0 @@ - $myparcelcomPaymentId, - 'paid_at' => $paidAt?->format(DateTimeInterface::ATOM), - 'failure_code' => $failureCode?->value, - 'failure_message' => $failureMessage, - ], static fn ($value) => $value !== null); - - if (Env::get('APP_ENV') === 'local') { - return $this->localClient->publish($payload); - } - - return $this->snsClient->publishAsync([ - 'Message' => Utils::jsonEncode($payload), - 'TopicArn' => $topicArn, - ]); - } -} diff --git a/tests/Sns/PublishJobTest.php b/tests/Publish/PublishJobTest.php similarity index 56% rename from tests/Sns/PublishJobTest.php rename to tests/Publish/PublishJobTest.php index 126dbae..3d57dea 100644 --- a/tests/Sns/PublishJobTest.php +++ b/tests/Publish/PublishJobTest.php @@ -9,8 +9,9 @@ use Mockery; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; use Mockery\MockInterface; -use MyParcelCom\Payments\Providers\Sns\Publisher; -use MyParcelCom\Payments\Providers\Sns\PublishJob; +use MyParcelCom\Payments\Providers\Publish\Message; +use MyParcelCom\Payments\Providers\Publish\Publisher; +use MyParcelCom\Payments\Providers\Publish\PublishJob; use PHPUnit\Framework\TestCase; class PublishJobTest extends TestCase @@ -20,32 +21,23 @@ class PublishJobTest extends TestCase public function test_it_handles_publish_job(): void { $faker = Factory::create(); - $topicArn = "arn:aws:sns:eu-west-1:{$faker->randomNumber()}:{$faker->word}"; - $myparcelcomPaymentId = $faker->uuid; - $paidAt = $faker->dateTime; $snsPromise = Mockery::mock(Promise::class, function (MockInterface & Promise $mock) { $mock->expects('wait'); }); + $message = Mockery::mock(Message::class); + $publisher = Mockery::mock(Publisher::class, function (MockInterface & Publisher $mock) use ( $snsPromise, - $topicArn, - $myparcelcomPaymentId, - $paidAt - ) { + $message) { $mock->expects('publish') - ->with( - $topicArn, - $myparcelcomPaymentId, - $paidAt, - null, - null, - ) + ->with($message) ->andReturns($snsPromise); }); - $job = new PublishJob($topicArn, $myparcelcomPaymentId, $paidAt); + + $job = new PublishJob($message); $job->handle($publisher); } } diff --git a/tests/Sns/PublisherTest.php b/tests/Publish/PublisherTest.php similarity index 59% rename from tests/Sns/PublisherTest.php rename to tests/Publish/PublisherTest.php index b6b9d99..aba9204 100644 --- a/tests/Sns/PublisherTest.php +++ b/tests/Publish/PublisherTest.php @@ -2,20 +2,20 @@ declare(strict_types=1); -namespace Tests\Sns; +namespace Tests\Publish; use Aws\Sns\SnsClient; use Faker\Factory; use GuzzleHttp\Promise\Promise; -use GuzzleHttp\Utils; -use Illuminate\Support\Env; use Mockery; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; use Mockery\MockInterface; -use MyParcelCom\Payments\Providers\Sns\EmptyPayloadException; -use MyParcelCom\Payments\Providers\Sns\FailureCode; -use MyParcelCom\Payments\Providers\Sns\LocalClient; -use MyParcelCom\Payments\Providers\Sns\Publisher; +use MyParcelCom\Payments\Providers\Publish\AuthorizationDisruption; +use MyParcelCom\Payments\Providers\Publish\FailureCode; +use MyParcelCom\Payments\Providers\Publish\LocalClient; +use MyParcelCom\Payments\Providers\Publish\PaymentFailed; +use MyParcelCom\Payments\Providers\Publish\PaymentSuccessful; +use MyParcelCom\Payments\Providers\Publish\Publisher; use PHPUnit\Framework\TestCase; class PublisherTest extends TestCase @@ -39,16 +39,21 @@ public function test_it_publishes_a_success_message_to_sns(): void ->expects('publishAsync') ->with([ 'Message' => json_encode([ - 'myparcelcom_payment_id' => $myparcelcomPaymentId, - 'paid_at' => $paidAt->format(DATE_ATOM), + 'type' => 'payment_successful', + 'data' => [ + 'myparcelcom_payment_id' => $myparcelcomPaymentId, + 'paid_at' => $paidAt->format(DATE_ATOM), + ], ], JSON_THROW_ON_ERROR), 'TopicArn' => $topicArn, ]) ->andReturns(Mockery::mock(Promise::class)); }); + $message = new PaymentSuccessful($myparcelcomPaymentId, $paidAt, $topicArn); + $publisher = new Publisher($snsClient, Mockery::mock(LocalClient::class)); - $publisher->publish($topicArn, $myparcelcomPaymentId, $paidAt); + $publisher->publish($message); } public function test_it_publishes_a_failure_message_to_sns(): void @@ -70,46 +75,54 @@ public function test_it_publishes_a_failure_message_to_sns(): void ->expects('publishAsync') ->with([ 'Message' => json_encode([ - 'myparcelcom_payment_id' => $myparcelcomPaymentId, - 'failure_code' => $failureCode, - 'failure_message' => $failureMessage, + 'type' => 'payment_failed', + 'data' => [ + 'myparcelcom_payment_id' => $myparcelcomPaymentId, + 'failure_code' => $failureCode, + 'failure_message' => $failureMessage, + ], ], JSON_THROW_ON_ERROR), 'TopicArn' => $topicArn, ]) ->andReturns(Mockery::mock(Promise::class)); }); + $message = new PaymentFailed($myparcelcomPaymentId, $failureCode, $failureMessage, $topicArn); + $publisher = new Publisher($snsClient, Mockery::mock(LocalClient::class)); - $publisher->publish($topicArn, $myparcelcomPaymentId, null, $failureCode, $failureMessage); + $publisher->publish($message); } - public function test_it_does_not_publish_message_to_sns_without_paid_at_or_failures(): void + public function test_it_publishes_authorization_disruption_to_sns(): void { - $this->expectException(EmptyPayloadException::class); - $faker = Factory::create(); $topicArn = "arn:aws:sns:eu-west-1:{$faker->randomNumber()}:{$faker->word}"; - $myparcelcomPaymentId = $faker->uuid; + $shopId = $faker->uuid; $snsClient = Mockery::mock(SnsClient::class, function (MockInterface & SnsClient $mock) use ( $topicArn, - $myparcelcomPaymentId + $shopId ) { $mock ->expects('publishAsync') - ->never() ->with([ 'Message' => json_encode([ - 'myparcelcom_payment_id' => $myparcelcomPaymentId, + 'type' => 'authorization_disruption', + 'data' => [ + 'shop_id' => $shopId, + 'payment_provider_code' => 'mollie', + ], ], JSON_THROW_ON_ERROR), 'TopicArn' => $topicArn, ]) ->andReturns(Mockery::mock(Promise::class)); }); + $message = new AuthorizationDisruption($shopId, 'mollie', $topicArn); + $publisher = new Publisher($snsClient, Mockery::mock(LocalClient::class)); - $publisher->publish($topicArn, $myparcelcomPaymentId); + $publisher->publish($message); } public function test_it_publishes_success_message_to_local_client(): void @@ -128,13 +141,18 @@ public function test_it_publishes_success_message_to_local_client(): void $mock ->expects('publish') ->with([ - 'myparcelcom_payment_id' => $myparcelcomPaymentId, - 'paid_at' => $paidAt->format(DATE_ATOM), + 'type' => 'payment_successful', + 'data' => [ + 'myparcelcom_payment_id' => $myparcelcomPaymentId, + 'paid_at' => $paidAt->format(DATE_ATOM), + ], ]); }); + $message = new PaymentSuccessful($myparcelcomPaymentId, $paidAt, $topicArn); + $publisher = new Publisher(Mockery::mock(SnsClient::class), $localClient); - $publisher->publish($topicArn, $myparcelcomPaymentId, $paidAt); + $publisher->publish($message); putenv('APP_ENV='); } @@ -146,6 +164,7 @@ public function test_it_publishes_fail_message_to_local_client(): void $topicArn = "arn:aws:sns:eu-west-1:{$faker->randomNumber()}:{$faker->word}"; $myparcelcomPaymentId = $faker->uuid; + /** @var FailureCode $failureCode */ $failureCode = $faker->randomElement(FailureCode::cases()); $failureMessage = $faker->sentence; @@ -157,14 +176,49 @@ public function test_it_publishes_fail_message_to_local_client(): void $mock ->expects('publish') ->with([ - 'myparcelcom_payment_id' => $myparcelcomPaymentId, - 'failure_code' => $failureCode->value, - 'failure_message' => $failureMessage, + 'type' => 'payment_failed', + 'data' => [ + 'myparcelcom_payment_id' => $myparcelcomPaymentId, + 'failure_code' => $failureCode->value, + 'failure_message' => $failureMessage, + ], ]); }); + $message = new PaymentFailed($myparcelcomPaymentId, $failureCode, $failureMessage, $topicArn); + + $publisher = new Publisher(Mockery::mock(SnsClient::class), $localClient); + $publisher->publish($message); + + putenv('APP_ENV='); + } + + public function test_it_publishes_authorization_disruption_to_local_client(): void + { + putenv('APP_ENV=local'); + $faker = Factory::create(); + + $topicArn = "arn:aws:sns:eu-west-1:{$faker->randomNumber()}:{$faker->word}"; + $shopId = $faker->uuid; + + $localClient = Mockery::mock(LocalClient::class, function (MockInterface & LocalClient $mock) use ( + $shopId + ) { + $mock + ->expects('publish') + ->with([ + 'type' => 'authorization_disruption', + 'data' => [ + 'shop_id' => $shopId, + 'payment_provider_code' => 'mollie', + ], + ]); + }); + + $message = new AuthorizationDisruption($shopId, 'mollie', $topicArn); + $publisher = new Publisher(Mockery::mock(SnsClient::class), $localClient); - $publisher->publish($topicArn, $myparcelcomPaymentId, null, $failureCode, $failureMessage); + $publisher->publish($message); putenv('APP_ENV='); }