Skip to content

Commit

Permalink
feat: added customer vault token route
Browse files Browse the repository at this point in the history
  • Loading branch information
FlorianKe committed Jan 30, 2025
1 parent d0cb467 commit 3655fae
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 85 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# 9.6.6
- PPI-1044 - Added a store api endpoint route that returns the vault token from the customer

# 9.6.5
- PPI-1025 - Improves the performance of the installment banner in the Storefront
- PPI-1043 - Fixes an issue, where a payment method is toggled twice in the Administration
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG_de-DE.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# 9.6.6
- PPI-1044 - Fügt eine Store-API hinzufügt, die den vault token vom Kunden zurück gibt

# 9.6.5
- PPI-1025 - Verbessert die Performance des Ratenzahlungsbanners in der Storefront
- PPI-1043 - Behebt ein Problem, bei dem eine Zahlungsmethode doppelt umgeschalten wurde
Expand Down
74 changes: 74 additions & 0 deletions src/Checkout/SalesChannel/CustomerVaultTokenRoute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php declare(strict_types=1);
/*
* (c) shopware AG <[email protected]>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Swag\PayPal\Checkout\SalesChannel;

use OpenApi\Attributes as OA;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\Log\Package;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Swag\PayPal\DataAbstractionLayer\VaultToken\VaultTokenEntity;
use Swag\PayPal\RestApi\V1\Resource\TokenResourceInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

#[Package('checkout')]
#[Route(defaults: ['_routeScope' => ['store-api']])]
readonly class CustomerVaultTokenRoute
{
/**
* @internal
*/
public function __construct(
private EntityRepository $vaultRepository,
private TokenResourceInterface $tokenResource,
) {
}

#[OA\Get(
path: '/paypal/vault-token',
operationId: 'getPayPalCustomerVaultToken',
description: 'Tries to get the customer vault token',
tags: ['Store API', 'PayPal'],
responses: [new OA\Response(
response: Response::HTTP_OK,
description: 'The customer vault token or null',
content: new OA\JsonContent(properties: [new OA\Property(
property: 'token',
type: 'string'
)])
)],
)]
#[Route(path: '/store-api/paypal/vault-token', name: 'store-api.paypal.vault.token', defaults: ['_loginRequired' => true], methods: ['GET'])]
public function getVaultToken(SalesChannelContext $context): JsonResponse
{
$customer = $context->getCustomer();
if ($customer === null || $customer->getGuest() === true) {
return new JsonResponse(['token' => null]);
}

$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('mainMapping.customerId', $customer->getId()));
$criteria->addFilter(new EqualsFilter('mainMapping.paymentMethodId', $context->getPaymentMethod()->getId()));

/** @var VaultTokenEntity|null $vault */
$vault = $this->vaultRepository->search($criteria, $context->getContext())->first();

if ($vault !== null) {
return new JsonResponse(
['token' => $this->tokenResource->getUserIdToken($context->getSalesChannelId(), $vault->getTokenCustomer())->getIdToken()]
);
}

return new JsonResponse(
['token' => $this->tokenResource->getUserIdToken($context->getSalesChannelId())->getIdToken()]
);
}
}
27 changes: 27 additions & 0 deletions src/Resources/Schema/StoreApi/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,33 @@
}
}
},
"/paypal/vault-token": {
"get": {
"tags": [
"Store API",
"PayPal"
],
"description": "Tries to get the customer vault token",
"operationId": "getPayPalCustomerVaultToken",
"responses": {
"200": {
"description": "The customer vault token or null",
"content": {
"application/json": {
"schema": {
"properties": {
"token": {
"type": "string"
}
},
"type": "object"
}
}
}
}
}
}
},
"/paypal/payment-method-eligibility": {
"post": {
"tags": [
Expand Down
5 changes: 5 additions & 0 deletions src/Resources/config/services/checkout.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@
<argument type="service" id="Shopware\Core\Checkout\Payment\Cart\PaymentTransactionStructFactory"/>
</service>

<service id="Swag\PayPal\Checkout\SalesChannel\CustomerVaultTokenRoute" public="true">
<argument type="service" id="swag_paypal_vault_token.repository"/>
<argument type="service" id="Swag\PayPal\RestApi\V1\Resource\TokenResource" />
</service>

<service id="Swag\PayPal\Checkout\SalesChannel\FilteredPaymentMethodRoute"
decorates="Shopware\Core\Checkout\Payment\SalesChannel\PaymentMethodRoute"
decoration-priority="-1500"
Expand Down
4 changes: 2 additions & 2 deletions src/Resources/config/services/storefront.xml
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@
<service id="Swag\PayPal\Storefront\Data\Service\PayLaterCheckoutDataService" public="true" parent="Swag\PayPal\Storefront\Data\Service\AbstractCheckoutDataService" />
<service id="Swag\PayPal\Storefront\Data\Service\SEPACheckoutDataService" public="true" parent="Swag\PayPal\Storefront\Data\Service\AbstractCheckoutDataService" />
<service id="Swag\PayPal\Storefront\Data\Service\SPBCheckoutDataService" public="true" parent="Swag\PayPal\Storefront\Data\Service\AbstractCheckoutDataService">
<argument type="service" id="Swag\PayPal\Storefront\Data\Service\VaultDataService"/>
<argument type="service" id="Swag\PayPal\Checkout\SalesChannel\CustomerVaultTokenRoute"/>
</service>
<service id="Swag\PayPal\Storefront\Data\Service\VenmoCheckoutDataService" public="true" parent="Swag\PayPal\Storefront\Data\Service\AbstractCheckoutDataService">
<argument type="service" id="Swag\PayPal\Storefront\Data\Service\VaultDataService"/>
<argument type="service" id="Swag\PayPal\Checkout\SalesChannel\CustomerVaultTokenRoute"/>
</service>

<service id="Swag\PayPal\Storefront\Data\CheckoutDataSubscriber">
Expand Down
10 changes: 8 additions & 2 deletions src/Storefront/Data/Service/SPBCheckoutDataService.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Swag\PayPal\Checkout\ExpressCheckout\SalesChannel\ExpressPrepareCheckoutRoute;
use Swag\PayPal\Checkout\SalesChannel\CustomerVaultTokenRoute;
use Swag\PayPal\Checkout\SPBCheckout\SPBCheckoutButtonData;
use Swag\PayPal\Setting\Service\CredentialsUtilInterface;
use Swag\PayPal\Setting\Settings;
Expand All @@ -37,7 +38,7 @@ public function __construct(
RouterInterface $router,
SystemConfigService $systemConfigService,
CredentialsUtilInterface $credentialsUtil,
private readonly VaultDataService $vaultDataService,
private readonly CustomerVaultTokenRoute $customerVaultTokenRoute,
) {
parent::__construct($paymentMethodDataRegistry, $localeCodeProvider, $router, $systemConfigService, $credentialsUtil);
}
Expand Down Expand Up @@ -75,7 +76,12 @@ public function buildCheckoutData(
'useAlternativePaymentMethods' => $this->systemConfigService->getBool(Settings::SPB_ALTERNATIVE_PAYMENT_METHODS_ENABLED, $salesChannelId),
'disabledAlternativePaymentMethods' => $this->getDisabledAlternativePaymentMethods($price, $currency->getIsoCode()),
'showPayLater' => $this->systemConfigService->getBool(Settings::SPB_SHOW_PAY_LATER, $salesChannelId),
'userIdToken' => $this->vaultDataService->getUserIdToken($context),
'userIdToken' => \json_decode(
(string) $this->customerVaultTokenRoute->getVaultToken($context)->getContent(),
true,
512,
\JSON_THROW_ON_ERROR
)['token'],
]));
}

Expand Down
3 changes: 3 additions & 0 deletions src/Storefront/Data/Service/VaultDataService.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ public function buildData(SalesChannelContext $context): ?VaultData
return $struct;
}

/**
* @deprecated tag:v10.0.0 - Will be removed. Use `CustomerVaultTokenRoute::getVaultToken` instead
*/
public function getUserIdToken(SalesChannelContext $context): ?string
{
$customer = $context->getCustomer();
Expand Down
10 changes: 8 additions & 2 deletions src/Storefront/Data/Service/VenmoCheckoutDataService.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Shopware\Core\Framework\Log\Package;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Swag\PayPal\Checkout\SalesChannel\CustomerVaultTokenRoute;
use Swag\PayPal\Setting\Service\CredentialsUtilInterface;
use Swag\PayPal\Storefront\Data\Struct\VenmoCheckoutData;
use Swag\PayPal\Util\Lifecycle\Method\PaymentMethodDataRegistry;
Expand All @@ -31,7 +32,7 @@ public function __construct(
RouterInterface $router,
SystemConfigService $systemConfigService,
CredentialsUtilInterface $credentialsUtil,
private readonly VaultDataService $vaultDataService,
private readonly CustomerVaultTokenRoute $customerVaultTokenRoute,
) {
parent::__construct($paymentMethodDataRegistry, $localeCodeProvider, $router, $systemConfigService, $credentialsUtil);
}
Expand All @@ -41,7 +42,12 @@ public function buildCheckoutData(SalesChannelContext $context, ?Cart $cart = nu
$data = $this->getBaseData($context, $order);

return (new VenmoCheckoutData())->assign(\array_merge($data, [
'userIdToken' => $this->vaultDataService->getUserIdToken($context),
'userIdToken' => \json_decode(
(string) $this->customerVaultTokenRoute->getVaultToken($context)->getContent(),
true,
512,
\JSON_THROW_ON_ERROR
)['token'],
]));
}

Expand Down
91 changes: 91 additions & 0 deletions tests/Checkout/SalesChannel/CustomerVaultTokenRouteTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php declare(strict_types=1);
/*
* (c) shopware AG <[email protected]>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Swag\PayPal\Test\Checkout\SalesChannel;

use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult;
use Shopware\Core\Framework\Log\Package;
use Shopware\Core\Test\Generator;
use Swag\PayPal\Checkout\SalesChannel\CustomerVaultTokenRoute;
use Swag\PayPal\DataAbstractionLayer\VaultToken\VaultTokenEntity;
use Swag\PayPal\RestApi\V1\Api\Token;
use Swag\PayPal\RestApi\V1\Resource\TokenResourceInterface;
use Swag\PayPal\Test\Helper\SalesChannelContextTrait;

/**
* @internal
*/
#[Package('checkout')]
class CustomerVaultTokenRouteTest extends TestCase
{
use SalesChannelContextTrait;

private EntityRepository&MockObject $repository;

private TokenResourceInterface&MockObject $tokenResource;

private CustomerVaultTokenRoute $route;

protected function setUp(): void
{
$this->repository = $this->createMock(EntityRepository::class);
$this->tokenResource = $this->createMock(TokenResourceInterface::class);

$this->route = new CustomerVaultTokenRoute($this->repository, $this->tokenResource);
}

public function testGetVaultTokenWithoutCustomer(): void
{
$salesChannelContext = Generator::generateSalesChannelContext();

Check failure on line 46 in tests/Checkout/SalesChannel/CustomerVaultTokenRouteTest.php

View workflow job for this annotation

GitHub Actions / phpstan (v6.6.0.0)

Call to an undefined static method Shopware\Core\Test\Generator::generateSalesChannelContext().
$salesChannelContext->assign(['customer' => null]);

$response = $this->route->getVaultToken($salesChannelContext);

static::assertSame(
'{"token":null}',
(string) $response->getContent()
);
}

public function testGetVaultTokenWithGuestCustomer(): void
{
$salesChannelContext = Generator::generateSalesChannelContext();

Check failure on line 59 in tests/Checkout/SalesChannel/CustomerVaultTokenRouteTest.php

View workflow job for this annotation

GitHub Actions / phpstan (v6.6.0.0)

Call to an undefined static method Shopware\Core\Test\Generator::generateSalesChannelContext().
$salesChannelContext->getCustomer()?->setGuest(true);

$response = $this->route->getVaultToken($salesChannelContext);

static::assertSame(
'{"token":null}',
(string) $response->getContent()
);
}

public function testGetVaultToken(): void
{
$salesChannelContext = Generator::generateSalesChannelContext();

Check failure on line 72 in tests/Checkout/SalesChannel/CustomerVaultTokenRouteTest.php

View workflow job for this annotation

GitHub Actions / phpstan (v6.6.0.0)

Call to an undefined static method Shopware\Core\Test\Generator::generateSalesChannelContext().
$salesChannelContext->getCustomer()?->setGuest(false);

$entitySearchResult = $this->createMock(EntitySearchResult::class);
$this->repository->expects(static::once())->method('search')->willReturn($entitySearchResult);
$entitySearchResult->expects(static::once())->method('first')->willReturn(new VaultTokenEntity());

$token = new Token();
$token->assign(['idToken' => 'dummy-token', 'expiresIn' => 45000]);

$this->tokenResource->expects(static::once())->method('getUserIdToken')->willReturn($token);

$response = $this->route->getVaultToken($salesChannelContext);

static::assertSame(
$token->getIdToken(),
\json_decode((string) $response->getContent(), true, 512, \JSON_THROW_ON_ERROR)['token']
);
}
}
79 changes: 0 additions & 79 deletions tests/Storefront/Data/Service/VaultDataServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,83 +159,4 @@ public function testBuildDataWithExistingToken(): void
static::assertSame('test-identifier', $data->getIdentifier());
static::assertSame('card', $data->getSnippetType());
}

public function testGetUserIdTokenWithGuestCustomer(): void
{
$salesChannelContext = Generator::createSalesChannelContext();
$salesChannelContext->getCustomer()?->setGuest(true);

$service = new VaultDataService(
new StaticEntityRepository([]),
$this->createMock(PaymentMethodDataRegistry::class),
$this->createMock(TokenResourceInterface::class),
);

$token = $service->getUserIdToken($salesChannelContext);
static::assertNull($token);
}

public function testGetUserIdTokenWithExistingToken(): void
{
$salesChannelContext = Generator::createSalesChannelContext();
$salesChannelContext->getCustomer()?->setGuest(false);

$token = new Token();
$token->setIdToken('test-id-token');
$tokenResource = $this->createMock(TokenResourceInterface::class);
$tokenResource
->expects(static::once())
->method('getUserIdToken')
->with($salesChannelContext->getSalesChannelId(), 'token-customer')
->willReturn($token);

$existingToken = new VaultTokenEntity();
$existingToken->setId(Uuid::randomHex());
$existingToken->setTokenCustomer('token-customer');

$repository = new StaticEntityRepository([static function (Criteria $criteria) use ($existingToken, $salesChannelContext): VaultTokenCollection {
static::assertInstanceOf(EqualsFilter::class, $criteria->getFilters()[0]);
static::assertSame('mainMapping.customerId', $criteria->getFilters()[0]->getField());
static::assertSame($salesChannelContext->getCustomerId(), $criteria->getFilters()[0]->getValue());

static::assertInstanceOf(EqualsFilter::class, $criteria->getFilters()[1]);
static::assertSame('mainMapping.paymentMethodId', $criteria->getFilters()[1]->getField());
static::assertSame($salesChannelContext->getPaymentMethod()->getId(), $criteria->getFilters()[1]->getValue());

return new VaultTokenCollection([$existingToken]);
}]);

$service = new VaultDataService(
$repository,
$this->createMock(PaymentMethodDataRegistry::class),
$tokenResource,
);

$token = $service->getUserIdToken($salesChannelContext);
static::assertSame('test-id-token', $token);
}

public function testGetUserIdTokenWithoutExistingToken(): void
{
$salesChannelContext = Generator::createSalesChannelContext();
$salesChannelContext->getCustomer()?->setGuest(false);

$token = new Token();
$token->setIdToken('test-id-token');
$tokenResource = $this->createMock(TokenResourceInterface::class);
$tokenResource
->expects(static::once())
->method('getUserIdToken')
->with($salesChannelContext->getSalesChannelId())
->willReturn($token);

$service = new VaultDataService(
new StaticEntityRepository([new VaultTokenCollection()]),
$this->createMock(PaymentMethodDataRegistry::class),
$tokenResource,
);

$token = $service->getUserIdToken($salesChannelContext);
static::assertSame('test-id-token', $token);
}
}

0 comments on commit 3655fae

Please sign in to comment.