Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Added support for publishing failure messages to SNS #6

Merged
merged 1 commit into from
Jan 30, 2024
Merged
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
12 changes: 12 additions & 0 deletions src/Sns/EmptyPayloadException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace MyParcelCom\Payments\Providers\Sns;

use RuntimeException;

class EmptyPayloadException extends RuntimeException
{

}
12 changes: 12 additions & 0 deletions src/Sns/FailureCode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace MyParcelCom\Payments\Providers\Sns;

enum FailureCode: string
{
case FAILED = 'failed';
case CANCELLED = 'cancelled';
case EXPIRED = 'expired';
}
10 changes: 9 additions & 1 deletion src/Sns/PublishJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,21 @@ public function __construct(
private readonly string $topicArn,
private readonly string $myparcelcomPaymentId,
private readonly ?DateTimeInterface $paidAt = null,
private readonly ?FailureCode $failureCode = null,
private readonly ?string $failureMessage = null,
) {
}

public function handle(Publisher $publisher): void
{
$publisher
->publish($this->topicArn, $this->myparcelcomPaymentId, $this->paidAt)
->publish(
$this->topicArn,
$this->myparcelcomPaymentId,
$this->paidAt,
$this->failureCode,
$this->failureMessage,
)
->wait();
}
}
21 changes: 16 additions & 5 deletions src/Sns/Publisher.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,30 @@ public function __construct(
) {
}

public function publish(string $topicArn, string $myparcelcomPaymentId, ?DateTimeInterface $paidAt = null): PromiseInterface
{
$payload = [
public function publish(
string $topicArn,
string $myparcelcomPaymentId,
?DateTimeInterface $paidAt = null,
?FailureCode $failureCode = null,
?string $failureMessage = null,
): PromiseInterface {
if ($paidAt === null && $failureCode === null) {
throw new EmptyPayloadException();
}

$payload = array_filter([
'myparcelcom_payment_id' => $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(array_filter($payload, static fn ($value) => $value !== null)),
'Message' => Utils::jsonEncode($payload),
'TopicArn' => $topicArn,
]);
}
Expand Down
4 changes: 4 additions & 0 deletions tests/Http/SetupResponseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Faker\Factory;
use Illuminate\Http\Request;
use JsonException;
use Mockery;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
use MyParcelCom\Payments\Providers\Http\SetupResponse;
Expand All @@ -28,6 +29,9 @@ public function test_it_returns_no_content_response_when_no_authorization_url_is
assertEmpty($response->getContent());
}

/**
* @throws JsonException
*/
public function test_it_returns_json_response_when_authorization_url_is_set(): void
{
$faker = Factory::create();
Expand Down
8 changes: 7 additions & 1 deletion tests/Sns/PublishJobTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,13 @@ public function test_it_handles_publish_job(): void
$paidAt
) {
$mock->expects('publish')
->with($topicArn, $myparcelcomPaymentId, $paidAt)
->with(
$topicArn,
$myparcelcomPaymentId,
$paidAt,
null,
null,
)
->andReturns($snsPromise);
});

Expand Down
73 changes: 70 additions & 3 deletions tests/Sns/PublisherTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
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 PHPUnit\Framework\TestCase;
Expand All @@ -20,7 +22,7 @@ class PublisherTest extends TestCase
{
use MockeryPHPUnitIntegration;

public function test_it_publishes_a_message_to_sns(): void
public function test_it_publishes_a_success_message_to_sns(): void
{
$faker = Factory::create();

Expand Down Expand Up @@ -49,10 +51,44 @@ public function test_it_publishes_a_message_to_sns(): void
$publisher->publish($topicArn, $myparcelcomPaymentId, $paidAt);
}

public function test_it_publishes_message_to_sns_without_paid_at(): void
public function test_it_publishes_a_failure_message_to_sns(): void
{
$faker = Factory::create();

$topicArn = "arn:aws:sns:eu-west-1:{$faker->randomNumber()}:{$faker->word}";
$myparcelcomPaymentId = $faker->uuid;
$failureCode = $faker->randomElement(FailureCode::cases());
$failureMessage = $faker->sentence;

$snsClient = Mockery::mock(SnsClient::class, function (MockInterface & SnsClient $mock) use (
$topicArn,
$myparcelcomPaymentId,
$failureCode,
$failureMessage
) {
$mock
->expects('publishAsync')
->with([
'Message' => json_encode([
'myparcelcom_payment_id' => $myparcelcomPaymentId,
'failure_code' => $failureCode,
'failure_message' => $failureMessage,
], JSON_THROW_ON_ERROR),
'TopicArn' => $topicArn,
])
->andReturns(Mockery::mock(Promise::class));
});

$publisher = new Publisher($snsClient, Mockery::mock(LocalClient::class));
$publisher->publish($topicArn, $myparcelcomPaymentId, null, $failureCode, $failureMessage);
}

public function test_it_does_not_publish_message_to_sns_without_paid_at_or_failures(): void
{
$this->expectException(EmptyPayloadException::class);

$faker = Factory::create();

$topicArn = "arn:aws:sns:eu-west-1:{$faker->randomNumber()}:{$faker->word}";
$myparcelcomPaymentId = $faker->uuid;

Expand All @@ -62,6 +98,7 @@ public function test_it_publishes_message_to_sns_without_paid_at(): void
) {
$mock
->expects('publishAsync')
->never()
->with([
'Message' => json_encode([
'myparcelcom_payment_id' => $myparcelcomPaymentId,
Expand All @@ -75,7 +112,7 @@ public function test_it_publishes_message_to_sns_without_paid_at(): void
$publisher->publish($topicArn, $myparcelcomPaymentId);
}

public function test_it_publishes_message_to_local_client(): void
public function test_it_publishes_success_message_to_local_client(): void
{
putenv('APP_ENV=local');
$faker = Factory::create();
Expand All @@ -101,4 +138,34 @@ public function test_it_publishes_message_to_local_client(): void

putenv('APP_ENV=');
}

public function test_it_publishes_fail_message_to_local_client(): void
{
putenv('APP_ENV=local');
$faker = Factory::create();

$topicArn = "arn:aws:sns:eu-west-1:{$faker->randomNumber()}:{$faker->word}";
$myparcelcomPaymentId = $faker->uuid;
$failureCode = $faker->randomElement(FailureCode::cases());
$failureMessage = $faker->sentence;

$localClient = Mockery::mock(LocalClient::class, function (MockInterface & LocalClient $mock) use (
$myparcelcomPaymentId,
$failureCode,
$failureMessage
) {
$mock
->expects('publish')
->with([
'myparcelcom_payment_id' => $myparcelcomPaymentId,
'failure_code' => $failureCode->value,
'failure_message' => $failureMessage,
]);
});

$publisher = new Publisher(Mockery::mock(SnsClient::class), $localClient);
$publisher->publish($topicArn, $myparcelcomPaymentId, null, $failureCode, $failureMessage);

putenv('APP_ENV=');
}
}
Loading