Skip to content

Commit

Permalink
Merge pull request #21 from shopware/next-37028/iaf-filter-gateway
Browse files Browse the repository at this point in the history
NEXT-37028 - IAP Filter Gateway
  • Loading branch information
lernhart authored Dec 16, 2024
2 parents ebace4c + 9195769 commit 7ece9cc
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 22 deletions.
61 changes: 42 additions & 19 deletions src/Context/ContextResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Shopware\App\SDK\Context\ActionButton\ActionButtonAction;
use Shopware\App\SDK\Context\Cart\Cart;
use Shopware\App\SDK\Context\Gateway\Checkout\CheckoutGatewayAction;
use Shopware\App\SDK\Context\Gateway\InAppFeatures\FilterAction;
use Shopware\App\SDK\Context\InAppPurchase\InAppPurchaseProvider;
use Shopware\App\SDK\Context\Module\ModuleAction;
use Shopware\App\SDK\Context\Order\Order;
Expand Down Expand Up @@ -39,12 +40,15 @@ public function __construct(private readonly InAppPurchaseProvider $inAppPurchas
{
}

/**
* @throws \JsonException
*/
public function assembleWebhook(RequestInterface $request, ShopInterface $shop): WebhookAction
{
$body = json_decode($request->getBody()->getContents(), true, flags: JSON_THROW_ON_ERROR);
$body = \json_decode($request->getBody()->getContents(), true, flags: \JSON_THROW_ON_ERROR);
$request->getBody()->rewind();

if (!is_array($body) || !isset($body['source']) || !is_array($body['source'])) {
if (!\is_array($body) || !isset($body['source']) || !\is_array($body['source'])) {
throw new MalformedWebhookBodyException();
}

Expand All @@ -59,10 +63,10 @@ public function assembleWebhook(RequestInterface $request, ShopInterface $shop):

public function assembleActionButton(RequestInterface $request, ShopInterface $shop): ActionButtonAction
{
$body = json_decode($request->getBody()->getContents(), true, flags: JSON_THROW_ON_ERROR);
$body = \json_decode($request->getBody()->getContents(), true, flags: \JSON_THROW_ON_ERROR);
$request->getBody()->rewind();

if (!is_array($body) || !isset($body['source']) || !is_array($body['source'])) {
if (!\is_array($body) || !isset($body['source']) || !\is_array($body['source'])) {
throw new MalformedWebhookBodyException();
}

Expand Down Expand Up @@ -108,10 +112,10 @@ public function assembleModule(RequestInterface $request, ShopInterface $shop):

public function assembleTaxProvider(RequestInterface $request, ShopInterface $shop): TaxProviderAction
{
$body = json_decode($request->getBody()->getContents(), true, flags: JSON_THROW_ON_ERROR);
$body = \json_decode($request->getBody()->getContents(), true, flags: \JSON_THROW_ON_ERROR);
$request->getBody()->rewind();

if (!is_array($body) || !isset($body['source']) || !is_array($body['source'])) {
if (!\is_array($body) || !isset($body['source']) || !\is_array($body['source'])) {
throw new MalformedWebhookBodyException();
}

Expand All @@ -125,10 +129,10 @@ public function assembleTaxProvider(RequestInterface $request, ShopInterface $sh

public function assemblePaymentPay(RequestInterface $request, ShopInterface $shop): PaymentPayAction
{
$body = json_decode($request->getBody()->getContents(), true, flags: JSON_THROW_ON_ERROR);
$body = \json_decode($request->getBody()->getContents(), true, flags: \JSON_THROW_ON_ERROR);
$request->getBody()->rewind();

if (!is_array($body) || !isset($body['source']) || !is_array($body['source'])) {
if (!\is_array($body) || !isset($body['source']) || !\is_array($body['source'])) {
throw new MalformedWebhookBodyException();
}

Expand All @@ -145,10 +149,10 @@ public function assemblePaymentPay(RequestInterface $request, ShopInterface $sho

public function assemblePaymentFinalize(RequestInterface $request, ShopInterface $shop): PaymentFinalizeAction
{
$body = json_decode($request->getBody()->getContents(), true, flags: JSON_THROW_ON_ERROR);
$body = \json_decode($request->getBody()->getContents(), true, flags: \JSON_THROW_ON_ERROR);
$request->getBody()->rewind();

if (!is_array($body) || !isset($body['source']) || !is_array($body['source'])) {
if (!\is_array($body) || !isset($body['source']) || !\is_array($body['source'])) {
throw new MalformedWebhookBodyException();
}

Expand All @@ -163,10 +167,10 @@ public function assemblePaymentFinalize(RequestInterface $request, ShopInterface

public function assemblePaymentCapture(RequestInterface $request, ShopInterface $shop): PaymentCaptureAction
{
$body = json_decode($request->getBody()->getContents(), true, flags: JSON_THROW_ON_ERROR);
$body = \json_decode($request->getBody()->getContents(), true, flags: \JSON_THROW_ON_ERROR);
$request->getBody()->rewind();

if (!is_array($body) || !isset($body['source']) || !is_array($body['source'])) {
if (!\is_array($body) || !isset($body['source']) || !\is_array($body['source'])) {
throw new MalformedWebhookBodyException();
}

Expand All @@ -182,10 +186,10 @@ public function assemblePaymentCapture(RequestInterface $request, ShopInterface

public function assemblePaymentRecurringCapture(RequestInterface $request, ShopInterface $shop): PaymentRecurringAction
{
$body = json_decode($request->getBody()->getContents(), true, flags: JSON_THROW_ON_ERROR);
$body = \json_decode($request->getBody()->getContents(), true, flags: \JSON_THROW_ON_ERROR);
$request->getBody()->rewind();

if (!is_array($body) || !isset($body['source']) || !is_array($body['source'])) {
if (!\is_array($body) || !isset($body['source']) || !\is_array($body['source'])) {
throw new MalformedWebhookBodyException();
}

Expand All @@ -199,10 +203,10 @@ public function assemblePaymentRecurringCapture(RequestInterface $request, ShopI

public function assemblePaymentValidate(RequestInterface $request, ShopInterface $shop): PaymentValidateAction
{
$body = json_decode($request->getBody()->getContents(), true, flags: JSON_THROW_ON_ERROR);
$body = \json_decode($request->getBody()->getContents(), true, flags: \JSON_THROW_ON_ERROR);
$request->getBody()->rewind();

if (!is_array($body) || !isset($body['source']) || !is_array($body['source'])) {
if (!\is_array($body) || !isset($body['source']) || !\is_array($body['source'])) {
throw new MalformedWebhookBodyException();
}

Expand All @@ -217,10 +221,10 @@ public function assemblePaymentValidate(RequestInterface $request, ShopInterface

public function assemblePaymentRefund(RequestInterface $request, ShopInterface $shop): RefundAction
{
$body = json_decode($request->getBody()->getContents(), true, flags: JSON_THROW_ON_ERROR);
$body = \json_decode($request->getBody()->getContents(), true, flags: \JSON_THROW_ON_ERROR);
$request->getBody()->rewind();

if (!is_array($body) || !isset($body['source']) || !is_array($body['source'])) {
if (!\is_array($body) || !isset($body['source']) || !\is_array($body['source'])) {
throw new MalformedWebhookBodyException();
}

Expand Down Expand Up @@ -288,9 +292,28 @@ public function assembleCheckoutGatewayRequest(RequestInterface $request, ShopIn
);
}

public function assembleInAppPurchasesFilterRequest(RequestInterface $request, ShopInterface $shop): FilterAction
{
$body = \json_decode($request->getBody()->getContents(), true, flags: \JSON_THROW_ON_ERROR);
$request->getBody()->rewind();

if (!\is_array($body) || !isset($body['source']) || !\is_array($body['source'])) {
throw new MalformedWebhookBodyException();
}

if (!isset($body['purchases']) || !\is_array($body['purchases'])) {
throw new MalformedWebhookBodyException();
}

return new FilterAction(
$shop,
$this->parseSource($body['source'], $shop),
new Collection($body['purchases'])
);
}

/**
* @param array<string, mixed> $source
* @return ActionSource
*/
private function parseSource(array $source, ShopInterface $shop): ActionSource
{
Expand Down
25 changes: 25 additions & 0 deletions src/Context/Gateway/InAppFeatures/FilterAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace Shopware\App\SDK\Context\Gateway\InAppFeatures;

use Shopware\App\SDK\Context\ActionSource;
use Shopware\App\SDK\Framework\Collection;
use Shopware\App\SDK\Shop\ShopInterface;

class FilterAction
{
/**
* Use this action to filter in-app purchases to be *available* to buy for the customer.
* In the ActionSource you can find any *active* purchases.
*
* @param Collection<string> $purchases - The list of purchases to filter for
*/
public function __construct(
public readonly ShopInterface $shop,
public readonly ActionSource $source,
public readonly Collection $purchases,
) {
}
}
32 changes: 32 additions & 0 deletions src/Response/InAppPurchasesResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Shopware\App\SDK\Response;

use Http\Discovery\Psr17Factory;
use Psr\Http\Message\ResponseInterface;
use Shopware\App\SDK\Framework\Collection;

class InAppPurchasesResponse
{
/**
* @param Collection<string> $purchases
*/
public static function filter(Collection $purchases): ResponseInterface
{
return self::createResponse(['purchases' => $purchases->all()]);
}

/**
* @param array<mixed> $data
*/
private static function createResponse(array $data): ResponseInterface
{
$psr = new Psr17Factory();

return $psr->createResponse(200)
->withHeader('Content-Type', 'application/json')
->withBody($psr->createStream(\json_encode($data, \JSON_THROW_ON_ERROR)));
}
}
92 changes: 89 additions & 3 deletions tests/Context/ContextResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1138,9 +1138,94 @@ public function testAssembleCheckoutGatewayRequest(): void
static::assertSame('id2', $shippingMethods->get('technicalName2'));
}

/**
* @dataProvider methodsProvider
*/
public function testAssembleInAppPurchasesFilterRequest(): void
{
$expectedPurchases = new Collection(['identifier-1', 'identifier-2', 'identifier-3']);

$inAppPurchaseProvider = $this->createMock(InAppPurchaseProvider::class);
$inAppPurchaseProvider
->method('decodePurchases')
->with('["identifier-1", "identifier-2", "identifier-3"]')
->willReturn($expectedPurchases);

$contextResolver = new ContextResolver($inAppPurchaseProvider);

$body = [
'source' => [
'url' => 'https://example.com',
'appVersion' => 'foo',
],
'purchases' => ['identifier-1', 'identifier-2', 'identifier-3'],
];

$request = new Request('POST', '/', [], \json_encode($body, \JSON_THROW_ON_ERROR));

$action = $contextResolver->assembleInAppPurchasesFilterRequest($request, $this->getShop());

static::assertSame('https://example.com', $action->source->url);
static::assertSame('foo', $action->source->appVersion);

$purchases = $action->purchases;
static::assertCount(3, $purchases);
static::assertInstanceOf(Collection::class, $purchases);
static::assertEquals($expectedPurchases, $purchases);
}

public function testAssembleInAppPurchasesWithEmptyPurchases(): void
{
$contextResolver = new ContextResolver($this->createMock(InAppPurchaseProvider::class));

$body = [
'source' => [
'url' => 'https://example.com',
'appVersion' => 'foo',
],
'purchases' => [],
];

$request = new Request('POST', '/', [], \json_encode($body, \JSON_THROW_ON_ERROR));

$action = $contextResolver->assembleInAppPurchasesFilterRequest($request, $this->getShop());

static::assertEmpty($action->purchases);
}

public function testAssembleInAppPurchasesWithMalformedPurchases(): void
{
$contextResolver = new ContextResolver($this->createMock(InAppPurchaseProvider::class));

$body = [
'source' => [
'url' => 'https://example.com',
'appVersion' => 'foo',
],
'purchases' => 'foo-bar',
];

$request = new Request('POST', '/', [], \json_encode($body, \JSON_THROW_ON_ERROR));

$this->expectException(MalformedWebhookBodyException::class);

$contextResolver->assembleInAppPurchasesFilterRequest($request, $this->getShop());
}

public function testAssembleInAppPurchasesWithMalformedSource(): void
{
$contextResolver = new ContextResolver($this->createMock(InAppPurchaseProvider::class));

$body = [
'source' => 'foo',
'purchases' => ['foo', 'bar'],
];

$request = new Request('POST', '/', [], \json_encode($body, \JSON_THROW_ON_ERROR));

$this->expectException(MalformedWebhookBodyException::class);

$contextResolver->assembleInAppPurchasesFilterRequest($request, $this->getShop());
}

#[DataProvider('methodsProvider')]
public function testBodyRewindIsCalled(string $method): void
{
$body = static::createMock(StreamInterface::class);
Expand Down Expand Up @@ -1216,6 +1301,7 @@ public static function methodsProvider(): iterable
yield ['assemblePaymentRefund'];
yield ['assemblePaymentRecurringCapture'];
yield ['assembleCheckoutGatewayRequest'];
yield ['assembleInAppPurchasesFilterRequest'];
}

/**
Expand Down
30 changes: 30 additions & 0 deletions tests/Context/Gateway/InAppFeatures/FilterActionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Shopware\App\SDK\Tests\Context\Gateway\InAppFeatures;

use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use Shopware\App\SDK\Context\ActionSource;
use Shopware\App\SDK\Context\Gateway\InAppFeatures\FilterAction;
use Shopware\App\SDK\Framework\Collection;
use Shopware\App\SDK\Test\MockShop;

#[CoversClass(FilterAction::class)]
class FilterActionTest extends TestCase
{
public function testConstruct(): void
{
$purchases = new Collection(['purchase-1', 'purchase-2', 'purchase-3']);

$shop = new MockShop('foo', 'https://example.com', 'secret');
$source = new ActionSource('https://example.com', '1.0.0', new Collection());

$action = new FilterAction($shop, $source, $purchases);

static::assertSame($shop, $action->shop);
static::assertSame($source, $action->source);
static::assertSame($purchases, $action->purchases);
}
}
24 changes: 24 additions & 0 deletions tests/Response/InAppPurchasesResponseTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Shopware\App\SDK\Tests\Response;

use PHPUnit\Framework\Attributes\CoversClass;
use Shopware\App\SDK\Framework\Collection;
use Shopware\App\SDK\Response\InAppPurchasesResponse;
use PHPUnit\Framework\TestCase;

#[CoversClass(InAppPurchasesResponse::class)]
class InAppPurchasesResponseTest extends TestCase
{
public function testPaid(): void
{
$purchases = new Collection(['foo', 'bar']);
$response = InAppPurchasesResponse::filter($purchases);

static::assertSame(200, $response->getStatusCode());
static::assertSame('application/json', $response->getHeaderLine('Content-Type'));
static::assertSame('{"purchases":["foo","bar"]}', $response->getBody()->getContents());
}
}

0 comments on commit 7ece9cc

Please sign in to comment.