Skip to content
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
6 changes: 6 additions & 0 deletions src/Context/ActionSource.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,20 @@
class ActionSource
{
/**
* @deprecated 6.0.0 - $shopwareVersion should not be nullable
*
* @param string $url The shop url
* @param string $appVersion The installed App version
* @param Collection<InAppPurchase> $inAppPurchases The active in-app-purchases
* @param ?string $shopwareVersion The Shopware version provided by the header sw-version
* @param ?string $userLanguage The ISO-Code of the language used by the storefront customer or admin user
*/
public function __construct(
public readonly string $url,
public readonly string $appVersion,
public readonly Collection $inAppPurchases,
public readonly ?string $shopwareVersion = null,
public readonly ?string $userLanguage = null,
) {
}
}
28 changes: 15 additions & 13 deletions src/Context/ContextResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@

return new WebhookAction(
$shop,
$this->parseSource($body['source'], $shop),
$this->parseSource($body['source'], $shop, $request),
$body['data']['event'],
$body['data']['payload'],
new DateTimeImmutable('@' . $body['timestamp'])
Expand All @@ -74,7 +74,7 @@

return new ActionButtonAction(
$shop,
$this->parseSource($body['source'], $shop),
$this->parseSource($body['source'], $shop, $request),
$body['data']['ids'],
$body['data']['entity'],
$body['data']['action']
Expand Down Expand Up @@ -119,7 +119,7 @@

return new TaxProviderAction(
$shop,
$this->parseSource($body['source'], $shop),
$this->parseSource($body['source'], $shop, $request),
new SalesChannelContext($body['context']),
new Cart($body['cart'])
);
Expand All @@ -136,7 +136,7 @@

return new PaymentPayAction(
$shop,
$this->parseSource($body['source'], $shop),
$this->parseSource($body['source'], $shop, $request),
new Order($body['order']),
new OrderTransaction($body['orderTransaction']),
$body['returnUrl'] ?? null,
Expand All @@ -156,7 +156,7 @@

return new PaymentFinalizeAction(
$shop,
$this->parseSource($body['source'], $shop),
$this->parseSource($body['source'], $shop, $request),
new OrderTransaction($body['orderTransaction']),
isset($body['recurring']) ? new RecurringData($body['recurring']) : null,
// Support both Shopware 6.7 (requestData) and 6.6 (queryParameters) for backward compatibility
Expand All @@ -175,7 +175,7 @@

return new PaymentCaptureAction(
$shop,
$this->parseSource($body['source'], $shop),
$this->parseSource($body['source'], $shop, $request),
new Order($body['order']),
new OrderTransaction($body['orderTransaction']),
isset($body['recurring']) ? new RecurringData($body['recurring']) : null,
Expand All @@ -194,7 +194,7 @@

return new PaymentRecurringAction(
$shop,
$this->parseSource($body['source'], $shop),
$this->parseSource($body['source'], $shop, $request),
new Order($body['order']),
new OrderTransaction($body['orderTransaction']),
isset($body['recurring']) ? new RecurringData($body['recurring']) : null,
Expand All @@ -212,7 +212,7 @@

return new PaymentValidateAction(
$shop,
$this->parseSource($body['source'], $shop),
$this->parseSource($body['source'], $shop, $request),
new Cart($body['cart']),
new SalesChannelContext($body['salesChannelContext']),
$body['requestData'] ?? []
Expand All @@ -230,7 +230,7 @@

return new RefundAction(
$shop,
$this->parseSource($body['source'], $shop),
$this->parseSource($body['source'], $shop, $request),
new Order($body['order']),
new Refund($body['refund']),
);
Expand Down Expand Up @@ -284,7 +284,7 @@

return new CheckoutGatewayAction(
$shop,
$this->parseSource($body['source'], $shop),
$this->parseSource($body['source'], $shop, $request),
new Cart($body['cart']),
new SalesChannelContext($body['salesChannelContext']),
new Collection($this->arrayFlip($body['paymentMethods'])),
Expand All @@ -303,7 +303,7 @@

return new ContextGatewayAction(
$shop,
$this->parseSource($body['source'], $shop),
$this->parseSource($body['source'], $shop, $request),
new Cart($body['cart']),
new SalesChannelContext($body['salesChannelContext']),
$body['data'],
Expand All @@ -325,15 +325,15 @@

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

/**
* @param array<string, mixed> $source
*/
private function parseSource(array $source, ShopInterface $shop): ActionSource
private function parseSource(array $source, ShopInterface $shop, RequestInterface $request): ActionSource
{
if (!isset($source['url'], $source['appVersion']) || !\is_string($source['url']) || !\is_string($source['appVersion'])) {
throw new MalformedWebhookBodyException();
Expand All @@ -352,6 +352,8 @@
$source['url'],
$source['appVersion'],
$inAppPurchases ?? new Collection(),
$request->getHeaderLine('sw-version') ?: null,
$request->getHeaderLine('sw-user-language') ?: null,
);
}

Expand All @@ -365,7 +367,7 @@
{
foreach ($source as $key => $value) {
if (!\is_scalar($value)) {
continue;

Check warning on line 370 in src/Context/ContextResolver.php

View workflow job for this annotation

GitHub Actions / unit

Escaped Mutant for Mutator "Continue_": @@ @@ { foreach ($source as $key => $value) { if (!\is_scalar($value)) { - continue; + break; } yield (string) $value => $key; } } }
}

yield ((string) $value) => $key;
Expand Down
16 changes: 16 additions & 0 deletions tests/Context/ActionSourceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,21 @@ public function testConstructDefaults(): void
static::assertSame($url, $source->url);
static::assertSame($version, $source->appVersion);
static::assertEquals(new Collection(), $source->inAppPurchases);
static::assertNull($source->shopwareVersion);
static::assertNull($source->userLanguage);
}

public function testConstruct(): void
{
$url = 'https://example.com';
$version = '1.0.0';

$source = new ActionSource($url, $version, new Collection(), '6.7.0.0', 'en-GB');

static::assertSame($url, $source->url);
static::assertSame($version, $source->appVersion);
static::assertEquals(new Collection(), $source->inAppPurchases);
static::assertSame('6.7.0.0', $source->shopwareVersion);
static::assertSame('en-GB', $source->userLanguage);
}
}
111 changes: 111 additions & 0 deletions tests/Context/ContextResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1637,6 +1637,53 @@ public function testParseInAppPurchasesInvalid(): void
$contextResolver->assembleWebhook($request, $this->getShop());
}

/**
* @param array<string, mixed> $body
*/
#[DataProvider('sourceBodyProvider')]
public function testParseSourceReadsRequestHeaders(string $method, array $body): void
{
$request = new Request('POST', '/', [
'sw-version' => '6.7.0.0',
'sw-user-language' => 'en-GB',
], \json_encode($body, \JSON_THROW_ON_ERROR));

$contextResolver = new ContextResolver($this->createMock(InAppPurchaseProvider::class));
$action = $contextResolver->$method($request, $this->getShop());

static::assertSame('6.7.0.0', $action->source->shopwareVersion);
static::assertSame('en-GB', $action->source->userLanguage);
}

/**
* @param array<string, mixed> $body
*/
#[DataProvider('sourceBodyProvider')]
public function testParseSourceWithoutRequestHeaders(string $method, array $body): void
{
$request = new Request('POST', '/', [], \json_encode($body, \JSON_THROW_ON_ERROR));

$contextResolver = new ContextResolver($this->createMock(InAppPurchaseProvider::class));
$action = $contextResolver->$method($request, $this->getShop());

static::assertNull($action->source->shopwareVersion);
static::assertNull($action->source->userLanguage);
}

public function testParseSourceWithEmptyRequestHeaders(): void
{
$request = new Request('POST', '/', [
'sw-version' => '',
'sw-user-language' => '',
], '{"source":{"url":"https://example.com","appVersion":"1.0.0"},"purchases":[]}');

$contextResolver = new ContextResolver($this->createMock(InAppPurchaseProvider::class));
$action = $contextResolver->assembleInAppPurchasesFilterRequest($request, $this->getShop());

static::assertNull($action->source->shopwareVersion);
static::assertNull($action->source->userLanguage);
}

/**
* @return iterable<string[]>
*/
Expand Down Expand Up @@ -1677,6 +1724,70 @@ public static function methodsProvider(): iterable
yield ['assembleInAppPurchasesFilterRequest'];
}

/**
* A minimal valid body per assemble method that resolves a source.
*
* @return iterable<string, array{string, array<string, mixed>}>
*/
public static function sourceBodyProvider(): iterable
{
$source = [
'source' => [
'url' => 'https://example.com',
'appVersion' => '1.0.0',
],
];

yield 'assembleWebhook' => ['assembleWebhook', $source + [
'data' => ['event' => 'order.placed', 'payload' => []],
'timestamp' => 123456789,
]];
yield 'assembleActionButton' => ['assembleActionButton', $source + [
'data' => ['ids' => ['123'], 'entity' => 'order', 'action' => 'open'],
]];
yield 'assembleTaxProvider' => ['assembleTaxProvider', $source + [
'context' => [],
'cart' => [],
]];
yield 'assemblePaymentPay' => ['assemblePaymentPay', $source + [
'order' => [],
'orderTransaction' => [],
]];
yield 'assemblePaymentFinalize' => ['assemblePaymentFinalize', $source + [
'orderTransaction' => [],
]];
yield 'assemblePaymentCapture' => ['assemblePaymentCapture', $source + [
'order' => [],
'orderTransaction' => [],
]];
yield 'assemblePaymentRecurringCapture' => ['assemblePaymentRecurringCapture', $source + [
'order' => [],
'orderTransaction' => [],
]];
yield 'assemblePaymentValidate' => ['assemblePaymentValidate', $source + [
'cart' => [],
'salesChannelContext' => [],
]];
yield 'assemblePaymentRefund' => ['assemblePaymentRefund', $source + [
'order' => [],
'refund' => [],
]];
yield 'assembleCheckoutGatewayRequest' => ['assembleCheckoutGatewayRequest', $source + [
'cart' => [],
'salesChannelContext' => [],
'paymentMethods' => [],
'shippingMethods' => [],
]];
yield 'assembleContextGatewayRequest' => ['assembleContextGatewayRequest', $source + [
'cart' => [],
'salesChannelContext' => [],
'data' => [],
]];
yield 'assembleInAppPurchasesFilterRequest' => ['assembleInAppPurchasesFilterRequest', $source + [
'purchases' => [],
]];
}

/**
* @return iterable<string[]>
*/
Expand Down
Loading