Skip to content

Commit

Permalink
feature #84 Authorization token caching (Zales0123)
Browse files Browse the repository at this point in the history
This PR was merged into the 1.0-dev branch.

Discussion
----------



Commits
-------

2e92d39 New PayPalCredentials entity, for token caching
de9929e Service for caching authorization token
92568b6 Use cached access token everywhere
43c7ac5 Adjust Travis configuration
6ee9c39 CS fixes
  • Loading branch information
SirDomin authored Sep 8, 2020
2 parents eaa9522 + 6ee9c39 commit a0b2898
Show file tree
Hide file tree
Showing 20 changed files with 481 additions and 61 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ before_install:
- echo "memory_limit=4096M" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
- mkdir -p "${SYLIUS_CACHE_DIR}"

- cp -R migrations/ tests/Application/src/Migrations/

install:
- composer install --no-interaction --prefer-dist
- (cd tests/Application && yarn install)
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@
# just for now, it will be eventually hardcoded (as we always want to use Sylius PayPal facilitator)
PAYPAL_FACILITATOR_URL='https://paypal.sylius.com'
```
5. Copy and apply migrations
```
cp -R vendor/sylius/paypal-plugin/migrations/ src/Migrations/
bin/console doctrine:migrations:migrate -n
```
> BEWARE!
Expand Down
36 changes: 36 additions & 0 deletions migrations/Version20200907102535.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20200907102535 extends AbstractMigration
{
public function getDescription() : string
{
return '';
}

public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');

$this->addSql('CREATE TABLE sylius_paypal_plugin_pay_pal_credentials (id VARCHAR(255) NOT NULL, payment_method_id INT DEFAULT NULL, access_token VARCHAR(255) NOT NULL, creation_time DATETIME NOT NULL, expiration_time DATETIME NOT NULL, INDEX IDX_C56F54AD5AA1164F (payment_method_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET UTF8 COLLATE `UTF8_unicode_ci` ENGINE = InnoDB');
$this->addSql('ALTER TABLE sylius_paypal_plugin_pay_pal_credentials ADD CONSTRAINT FK_C56F54AD5AA1164F FOREIGN KEY (payment_method_id) REFERENCES sylius_payment_method (id)');
}

public function down(Schema $schema) : void
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');

$this->addSql('DROP TABLE sylius_paypal_plugin_pay_pal_credentials');
}
}
1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ includes:
parameters:
reportUnmatchedIgnoredErrors: false
checkMissingIterableValueType: false
checkGenericClassInNonGenericObjectType: false

excludes_analyse:
# Makes PHPStan crash
Expand Down
118 changes: 118 additions & 0 deletions spec/Api/CacheAuthorizeClientApiSpec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace spec\Sylius\PayPalPlugin\Api;

use Doctrine\Persistence\ObjectManager;
use Doctrine\Persistence\ObjectRepository;
use Payum\Core\Model\GatewayConfigInterface;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Component\Core\Model\PaymentMethodInterface;
use Sylius\PayPalPlugin\Api\AuthorizeClientApiInterface;
use Sylius\PayPalPlugin\Api\CacheAuthorizeClientApiInterface;
use Sylius\PayPalPlugin\Entity\PayPalCredentialsInterface;

final class CacheAuthorizeClientApiSpec extends ObjectBehavior
{
function let(
ObjectManager $payPalCredentialsManager,
ObjectRepository $payPalCredentialsRepository,
AuthorizeClientApiInterface $authorizeClientApi
): void {
$this->beConstructedWith($payPalCredentialsManager, $payPalCredentialsRepository, $authorizeClientApi);
}

function it_implements_cache_authorize_client_api_interface(): void
{
$this->shouldImplement(CacheAuthorizeClientApiInterface::class);
}

function it_returns_cached_access_token_if_it_is_not_expired(
ObjectRepository $payPalCredentialsRepository,
PayPalCredentialsInterface $payPalCredentials,
PaymentMethodInterface $paymentMethod
): void {
$payPalCredentialsRepository->findOneBy(['paymentMethod' => $paymentMethod])->willReturn($payPalCredentials);

$payPalCredentials->isExpired()->willReturn(false);
$payPalCredentials->accessToken()->willReturn('TOKEN');

$this->authorize($paymentMethod)->shouldReturn('TOKEN');
}

function it_gets_access_token_from_api_caches_and_returns_it(
ObjectManager $payPalCredentialsManager,
ObjectRepository $payPalCredentialsRepository,
AuthorizeClientApiInterface $authorizeClientApi,
PaymentMethodInterface $paymentMethod,
GatewayConfigInterface $gatewayConfig
): void {
$payPalCredentialsRepository->findOneBy(['paymentMethod' => $paymentMethod])->willReturn(null);

$paymentMethod->getGatewayConfig()->willReturn($gatewayConfig);
$gatewayConfig->getConfig()->willReturn(['client_id' => 'CLIENT_ID', 'client_secret' => '$ECRET']);

$authorizeClientApi->authorize('CLIENT_ID', '$ECRET')->willReturn('TOKEN');

$payPalCredentialsManager
->persist(Argument::that(function (PayPalCredentialsInterface $payPalCredentials) use ($paymentMethod): bool {
return
$payPalCredentials->accessToken() === 'TOKEN' &&
$payPalCredentials->creationTime()->format('d-m-Y H:i') === (new \DateTime())->format('d-m-Y H:i') &&
$payPalCredentials->expirationTime()->format('d-m-Y H:i') === (new \DateTime())->modify('+3600 seconds')->format('d-m-Y H:i') &&
$payPalCredentials->paymentMethod() === $paymentMethod->getWrappedObject()
;
}))
->shouldBeCalled()
;
$payPalCredentialsManager->flush()->shouldBeCalled();

$this->authorize($paymentMethod)->shouldReturn('TOKEN');
}

function it_returns_expired_token_and_ask_for_a_new_one(
ObjectManager $payPalCredentialsManager,
ObjectRepository $payPalCredentialsRepository,
AuthorizeClientApiInterface $authorizeClientApi,
PaymentMethodInterface $paymentMethod,
GatewayConfigInterface $gatewayConfig,
PayPalCredentialsInterface $payPalCredentials
): void {
$payPalCredentialsRepository->findOneBy(['paymentMethod' => $paymentMethod])->willReturn($payPalCredentials);
$payPalCredentials->isExpired()->willReturn(true);

$payPalCredentialsManager->remove($payPalCredentials)->shouldBeCalled();
$payPalCredentialsManager->flush()->shouldBeCalledTimes(2);

$paymentMethod->getGatewayConfig()->willReturn($gatewayConfig);
$gatewayConfig->getConfig()->willReturn(['client_id' => 'CLIENT_ID', 'client_secret' => '$ECRET']);

$authorizeClientApi->authorize('CLIENT_ID', '$ECRET')->willReturn('TOKEN');

$payPalCredentialsManager
->persist(Argument::that(function (PayPalCredentialsInterface $payPalCredentials) use ($paymentMethod): bool {
return
$payPalCredentials->accessToken() === 'TOKEN' &&
$payPalCredentials->creationTime()->format('d-m-Y H:i') == (new \DateTime())->format('d-m-Y H:i') &&
$payPalCredentials->expirationTime()->format('d-m-Y H:i') == (new \DateTime())->modify('+3600 seconds')->format('d-m-Y H:i') &&
$payPalCredentials->paymentMethod() === $paymentMethod->getWrappedObject()
;
}))
->shouldBeCalled()
;
$payPalCredentialsManager->flush()->shouldBeCalled();

$this->authorize($paymentMethod)->shouldReturn('TOKEN');
}
}
56 changes: 56 additions & 0 deletions spec/Entity/PayPalCredentialsSpec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace spec\Sylius\PayPalPlugin\Entity;

use PhpSpec\ObjectBehavior;
use Sylius\Component\Core\Model\PaymentMethodInterface;
use Sylius\PayPalPlugin\Entity\PayPalCredentialsInterface;

final class PayPalCredentialsSpec extends ObjectBehavior
{
function let(PaymentMethodInterface $paymentMethod): void
{
$this->beConstructedWith('123ASD123', $paymentMethod, 'TOKEN', new \DateTime('2020-01-01 10:00:00'), 3600);
}

function it_implements_pay_pal_credentials_interface(): void
{
$this->shouldImplement(PayPalCredentialsInterface::class);
}

function it_has_a_payment_method(PaymentMethodInterface $paymentMethod): void
{
$this->paymentMethod()->shouldReturn($paymentMethod);
}

function it_has_a_access_token(): void
{
$this->accessToken()->shouldReturn('TOKEN');
}

function it_has_a_creation_time(): void
{
$this->creationTime()->shouldBeLike(new \DateTime('2020-01-01 10:00:00'));
}

function it_has_a_expiration_time(): void
{
$this->expirationTime()->shouldBeLike(new \DateTime('2020-01-01 11:00:00'));
}

function it_can_be_expired(): void
{
$this->isExpired()->shouldReturn(true);
}
}
14 changes: 5 additions & 9 deletions spec/Payum/Action/CaptureActionSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,20 @@

use Payum\Core\Action\ActionInterface;
use Payum\Core\Exception\RequestNotSupportedException;
use Payum\Core\Model\GatewayConfigInterface;
use Payum\Core\Request\Capture;
use PhpSpec\ObjectBehavior;
use Sylius\Bundle\PayumBundle\Request\GetStatus;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Model\PaymentInterface;
use Sylius\Component\Core\Model\PaymentMethodInterface;
use Sylius\PayPalPlugin\Api\AuthorizeClientApiInterface;
use Sylius\PayPalPlugin\Api\CacheAuthorizeClientApiInterface;
use Sylius\PayPalPlugin\Api\CreateOrderApiInterface;
use Sylius\PayPalPlugin\Payum\Action\StatusAction;

final class CaptureActionSpec extends ObjectBehavior
{
function let(
AuthorizeClientApiInterface $authorizeClientApi,
CacheAuthorizeClientApiInterface $authorizeClientApi,
CreateOrderApiInterface $createOrderApi
): void {
$this->beConstructedWith($authorizeClientApi, $createOrderApi);
Expand All @@ -41,24 +40,21 @@ function it_implements_action_interface(): void
}

function it_authorizes_seller_send_create_order_request_and_sets_order_response_data_on_payment(
AuthorizeClientApiInterface $authorizeClientApi,
CacheAuthorizeClientApiInterface $authorizeClientApi,
CreateOrderApiInterface $createOrderApi,
Capture $request,
OrderInterface $order,
PaymentInterface $payment,
PaymentMethodInterface $paymentMethod,
GatewayConfigInterface $gatewayConfig
PaymentMethodInterface $paymentMethod
): void {
$request->getModel()->willReturn($payment);
$payment->getMethod()->willReturn($paymentMethod);
$paymentMethod->getGatewayConfig()->willReturn($gatewayConfig);
$gatewayConfig->getConfig()->willReturn(['client_id' => 'CLIENT_ID', 'client_secret' => 'CLIENT_SECRET']);

$payment->getAmount()->willReturn(1000);
$payment->getOrder()->willReturn($order);
$order->getCurrencyCode()->willReturn('USD');

$authorizeClientApi->authorize('CLIENT_ID', 'CLIENT_SECRET')->willReturn('ACCESS_TOKEN');
$authorizeClientApi->authorize($paymentMethod)->willReturn('ACCESS_TOKEN');
$createOrderApi->create('ACCESS_TOKEN', $payment)->willReturn(['status' => 'CREATED', 'id' => '123123']);

$payment->setDetails([
Expand Down
19 changes: 8 additions & 11 deletions spec/Processor/PayPalPaymentRefundProcessorSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@
use Prophecy\Argument;
use Sylius\Component\Core\Model\PaymentInterface;
use Sylius\Component\Core\Model\PaymentMethodInterface;
use Sylius\PayPalPlugin\Api\AuthorizeClientApiInterface;
use Sylius\PayPalPlugin\Api\CacheAuthorizeClientApiInterface;
use Sylius\PayPalPlugin\Api\RefundPaymentApiInterface;
use Sylius\PayPalPlugin\Exception\PayPalOrderRefundException;
use Sylius\PayPalPlugin\Processor\PaymentRefundProcessorInterface;

final class PayPalPaymentRefundProcessorSpec extends ObjectBehavior
{
function let(AuthorizeClientApiInterface $authorizeClientApi, RefundPaymentApiInterface $refundOrderApi): void
function let(CacheAuthorizeClientApiInterface $authorizeClientApi, RefundPaymentApiInterface $refundOrderApi): void
{
$this->beConstructedWith($authorizeClientApi, $refundOrderApi);
}
Expand All @@ -37,7 +37,7 @@ function it_implements_payment_refund_processor_interface(): void
}

function it_fully_refunds_payment_in_pay_pal(
AuthorizeClientApiInterface $authorizeClientApi,
CacheAuthorizeClientApiInterface $authorizeClientApi,
RefundPaymentApiInterface $refundOrderApi,
PaymentInterface $payment,
PaymentMethodInterface $paymentMethod,
Expand All @@ -46,10 +46,9 @@ function it_fully_refunds_payment_in_pay_pal(
$payment->getMethod()->willReturn($paymentMethod);
$paymentMethod->getGatewayConfig()->willReturn($gatewayConfig);
$gatewayConfig->getFactoryName()->willReturn('sylius.pay_pal');
$gatewayConfig->getConfig()->willReturn(['client_id' => 'CLIENT_ID', 'client_secret' => 'CLIENT_SECRET']);
$payment->getDetails()->willReturn(['paypal_payment_id' => '123123']);

$authorizeClientApi->authorize('CLIENT_ID', 'CLIENT_SECRET')->willReturn('TOKEN');
$authorizeClientApi->authorize($paymentMethod)->willReturn('TOKEN');
$refundOrderApi->refund('TOKEN', '123123')->willReturn(['status' => 'COMPLETED', 'id' => '123123']);

$this->refund($payment);
Expand Down Expand Up @@ -89,7 +88,7 @@ function it_does_nothing_if_payment_is_payment_has_not_pay_pal_payment_id(
}

function it_throws_exception_if_refund_could_not_be_processed(
AuthorizeClientApiInterface $authorizeClientApi,
CacheAuthorizeClientApiInterface $authorizeClientApi,
RefundPaymentApiInterface $refundOrderApi,
PaymentInterface $payment,
PaymentMethodInterface $paymentMethod,
Expand All @@ -98,10 +97,9 @@ function it_throws_exception_if_refund_could_not_be_processed(
$payment->getMethod()->willReturn($paymentMethod);
$paymentMethod->getGatewayConfig()->willReturn($gatewayConfig);
$gatewayConfig->getFactoryName()->willReturn('sylius.pay_pal');
$gatewayConfig->getConfig()->willReturn(['client_id' => 'CLIENT_ID', 'client_secret' => 'CLIENT_SECRET']);
$payment->getDetails()->willReturn(['paypal_payment_id' => '123123']);

$authorizeClientApi->authorize('CLIENT_ID', 'CLIENT_SECRET')->willReturn('TOKEN');
$authorizeClientApi->authorize($paymentMethod)->willReturn('TOKEN');
$refundOrderApi->refund('TOKEN', '123123')->willReturn(['status' => 'FAILED']);

$this
Expand All @@ -111,7 +109,7 @@ function it_throws_exception_if_refund_could_not_be_processed(
}

function it_throws_exception_if_something_went_wrong_during_refunding_payment(
AuthorizeClientApiInterface $authorizeClientApi,
CacheAuthorizeClientApiInterface $authorizeClientApi,
RefundPaymentApiInterface $refundOrderApi,
PaymentInterface $payment,
PaymentMethodInterface $paymentMethod,
Expand All @@ -120,10 +118,9 @@ function it_throws_exception_if_something_went_wrong_during_refunding_payment(
$payment->getMethod()->willReturn($paymentMethod);
$paymentMethod->getGatewayConfig()->willReturn($gatewayConfig);
$gatewayConfig->getFactoryName()->willReturn('sylius.pay_pal');
$gatewayConfig->getConfig()->willReturn(['client_id' => 'CLIENT_ID', 'client_secret' => 'CLIENT_SECRET']);
$payment->getDetails()->willReturn(['paypal_payment_id' => '123123']);

$authorizeClientApi->authorize('CLIENT_ID', 'CLIENT_SECRET')->willReturn('TOKEN');
$authorizeClientApi->authorize($paymentMethod)->willReturn('TOKEN');
$refundOrderApi->refund('TOKEN', '123123')->willThrow(ClientException::class);

$this
Expand Down
Loading

0 comments on commit a0b2898

Please sign in to comment.