Skip to content

Commit

Permalink
Add encrypting a Tpay payment gateway config (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
jakubtobiasz authored Sep 17, 2024
2 parents c40d0c8 + 98f045a commit 94aabff
Show file tree
Hide file tree
Showing 17 changed files with 309 additions and 7 deletions.
7 changes: 4 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,17 @@
},
"require": {
"php": "^8.0",
"sylius/core-bundle": "^1.12",
"tpay-com/tpay-openapi-php": "^1.8",
"defuse/php-encryption": "^2.4",
"payum/core": "^1.7",
"sylius/core-bundle": "^1.12",
"sylius/resource-bundle": "^1.10",
"symfony/config": "5.4.* || ^6.0",
"symfony/dependency-injection": "5.4.* || ^6.0",
"symfony/form": "5.4.* || ^6.0",
"symfony/http-foundation": "5.4.* || ^6.0",
"symfony/http-kernel": "5.4.* || ^6.0",
"symfony/routing": "5.4.* || ^6.0"
"symfony/routing": "5.4.* || ^6.0",
"tpay-com/tpay-openapi-php": "^1.8"
},
"conflict": {
"sylius/sylius": "<1.12"
Expand Down
15 changes: 15 additions & 0 deletions config/config/payum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use Symfony\Config\PayumConfig;

return function(PayumConfig $payum): void {
$payum
->dynamicGateways()
->encryption()
->defuseSecretKey('%env(PAYUM_CYPHER_KEY)%')
;
};
6 changes: 3 additions & 3 deletions config/config/sylius_fixtures.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
'code' => 'tpay',
'name' => 'Tpay',
'gatewayFactory' => 'tpay',
'gatewayName' => 'Tpay',
'gatewayName' => 'tpay',
'gatewayConfig' => [
'client_id' => '%env(string:TPAY_CLIENT_ID)%',
'client_secret' => '%env(string:TPAY_CLIENT_SECRET)%',
Expand All @@ -71,7 +71,7 @@
'code' => 'tpay_card',
'name' => 'Card (Tpay)',
'gatewayFactory' => 'tpay',
'gatewayName' => 'Tpay',
'gatewayName' => 'tpay',
'gatewayConfig' => [
'client_id' => '%env(string:TPAY_CLIENT_ID)%',
'client_secret' => '%env(string:TPAY_CLIENT_SECRET)%',
Expand All @@ -88,7 +88,7 @@
'code' => 'tpay_blik',
'name' => 'Blik (Tpay)',
'gatewayFactory' => 'tpay',
'gatewayName' => 'Tpay',
'gatewayName' => 'tpay',
'gatewayConfig' => [
'client_id' => '%env(string:TPAY_CLIENT_ID)%',
'client_secret' => '%env(string:TPAY_CLIENT_SECRET)%',
Expand Down
19 changes: 19 additions & 0 deletions config/services/fixtures.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use CommerceWeavers\SyliusTpayPlugin\Fixture\Factory\PaymentMethodExampleFactory;

return function(ContainerConfigurator $container): void {
$services = $container->services();

$services->set('commerce_weavers_tpay.fixture.factory.payment_method_example', PaymentMethodExampleFactory::class)
->decorate('sylius.fixture.example_factory.payment_method')
->args([
service('.inner'),
service('payum.dynamic_gateways.cypher'),
])
;
};
18 changes: 18 additions & 0 deletions config/services/form.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use CommerceWeavers\SyliusTpayPlugin\Form\DataTransformer\CardTypeDataTransformer;
use CommerceWeavers\SyliusTpayPlugin\Form\EventListener\DecryptGatewayConfigListener;
use CommerceWeavers\SyliusTpayPlugin\Form\EventListener\EncryptGatewayConfigListener;
use CommerceWeavers\SyliusTpayPlugin\Form\EventListener\PreventSavingEmptyClientSecretListener;
use CommerceWeavers\SyliusTpayPlugin\Form\EventListener\RemoveUnnecessaryPaymentDetailsFieldsListener;
use CommerceWeavers\SyliusTpayPlugin\Form\Extension\CompleteTypeExtension;
Expand All @@ -22,6 +24,8 @@

$services->set(TpayGatewayConfigurationType::class)
->args([
service('commerce_weavers_tpay.form.event_listener.decrypt_gateway_config'),
service('commerce_weavers_tpay.form.event_listener.encrypt_gateway_config'),
service('commerce_weavers_tpay.form.event_listener.prevent_saving_empty_client_secret'),
])
->tag(
Expand All @@ -47,6 +51,20 @@

$services->set('commerce_weavers_tpay.form.data_transformer.card_type', CardTypeDataTransformer::class);

$services
->set('commerce_weavers_tpay.form.event_listener.decrypt_gateway_config', DecryptGatewayConfigListener::class)
->args([
service('payum.dynamic_gateways.cypher'),
])
;

$services
->set('commerce_weavers_tpay.form.event_listener.encrypt_gateway_config', EncryptGatewayConfigListener::class)
->args([
service('payum.dynamic_gateways.cypher'),
])
;

$services->set('commerce_weavers_tpay.form.event_listener.prevent_saving_empty_client_secret', PreventSavingEmptyClientSecretListener::class);

$services->set('commerce_weavers_tpay.form.event_listener.remove_unnecessary_payment_details_fields', RemoveUnnecessaryPaymentDetailsFieldsListener::class);
Expand Down
42 changes: 42 additions & 0 deletions src/Fixture/Factory/PaymentMethodExampleFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace CommerceWeavers\SyliusTpayPlugin\Fixture\Factory;

use CommerceWeavers\SyliusTpayPlugin\Payum\Factory\TpayGatewayFactory;
use Payum\Core\Security\CryptedInterface;
use Payum\Core\Security\CypherInterface;
use Sylius\Bundle\CoreBundle\Fixture\Factory\ExampleFactoryInterface;
use Sylius\Component\Core\Model\PaymentMethodInterface;
use Webmozart\Assert\Assert;

final class PaymentMethodExampleFactory implements ExampleFactoryInterface
{
public function __construct(
private ExampleFactoryInterface $decorated,
private CypherInterface $cypher,
) {
}

public function create(array $options = []): PaymentMethodInterface
{
/** @var PaymentMethodInterface|mixed $paymentMethod */
$paymentMethod = $this->decorated->create($options);

Assert::isInstanceOf($paymentMethod, PaymentMethodInterface::class);

$gatewayConfig = $paymentMethod->getGatewayConfig();
Assert::notNull($gatewayConfig);

if ($gatewayConfig->getGatewayName() !== TpayGatewayFactory::NAME) {
return $paymentMethod;
}

if ($gatewayConfig instanceof CryptedInterface) {
$gatewayConfig->encrypt($this->cypher);
}

return $paymentMethod;
}
}
30 changes: 30 additions & 0 deletions src/Form/EventListener/DecryptGatewayConfigListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace CommerceWeavers\SyliusTpayPlugin\Form\EventListener;

use Payum\Core\Security\CryptedInterface;
use Payum\Core\Security\CypherInterface;
use Symfony\Component\Form\Event\PreSetDataEvent;

final class DecryptGatewayConfigListener implements DecryptGatewayConfigListenerInterface
{
public function __construct(
private CypherInterface $cypher,
) {
}

public function __invoke(PreSetDataEvent $event): void
{
$gatewayConfig = $event->getData();

if (!$gatewayConfig instanceof CryptedInterface) {
return;
}

$gatewayConfig->decrypt($this->cypher);

$event->setData($gatewayConfig);
}
}
12 changes: 12 additions & 0 deletions src/Form/EventListener/DecryptGatewayConfigListenerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace CommerceWeavers\SyliusTpayPlugin\Form\EventListener;

use Symfony\Component\Form\Event\PreSetDataEvent;

interface DecryptGatewayConfigListenerInterface
{
public function __invoke(PreSetDataEvent $event): void;
}
30 changes: 30 additions & 0 deletions src/Form/EventListener/EncryptGatewayConfigListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace CommerceWeavers\SyliusTpayPlugin\Form\EventListener;

use Payum\Core\Security\CryptedInterface;
use Payum\Core\Security\CypherInterface;
use Symfony\Component\Form\Event\PostSubmitEvent;

final class EncryptGatewayConfigListener implements EncryptGatewayConfigListenerInterface
{
public function __construct(
private CypherInterface $cypher,
) {
}

public function __invoke(PostSubmitEvent $event): void
{
$gatewayConfig = $event->getData();

if (!$gatewayConfig instanceof CryptedInterface) {
return;
}

$gatewayConfig->encrypt($this->cypher);

$event->setData($gatewayConfig);
}
}
12 changes: 12 additions & 0 deletions src/Form/EventListener/EncryptGatewayConfigListenerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace CommerceWeavers\SyliusTpayPlugin\Form\EventListener;

use Symfony\Component\Form\Event\PostSubmitEvent;

interface EncryptGatewayConfigListenerInterface
{
public function __invoke(PostSubmitEvent $event): void;
}
6 changes: 6 additions & 0 deletions src/Form/Type/TpayGatewayConfigurationType.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace CommerceWeavers\SyliusTpayPlugin\Form\Type;

use CommerceWeavers\SyliusTpayPlugin\Form\EventListener\DecryptGatewayConfigListenerInterface;
use CommerceWeavers\SyliusTpayPlugin\Form\EventListener\EncryptGatewayConfigListenerInterface;
use CommerceWeavers\SyliusTpayPlugin\Form\EventListener\PreventSavingEmptyClientSecretListener;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
Expand All @@ -16,6 +18,8 @@
final class TpayGatewayConfigurationType extends AbstractType
{
public function __construct(
private DecryptGatewayConfigListenerInterface $decryptGatewayConfigListener,
private EncryptGatewayConfigListenerInterface $encryptGatewayConfigListener,
private PreventSavingEmptyClientSecretListener $preventSavingEmptyClientSecretListener,
) {
}
Expand Down Expand Up @@ -69,6 +73,8 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
)
;

$builder->addEventListener(FormEvents::PRE_SET_DATA, $this->decryptGatewayConfigListener);
$builder->addEventListener(FormEvents::POST_SUBMIT, $this->encryptGatewayConfigListener);
$builder->addEventListener(FormEvents::PRE_SUBMIT, $this->preventSavingEmptyClientSecretListener);
}
}
2 changes: 1 addition & 1 deletion src/Payum/Factory/TpayGatewayFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ protected function populateConfig(ArrayObject $config): void
{
$config->defaults([
'payum.factory_name' => self::NAME,
'payum.factory_title' => ucfirst(self::NAME),
'payum.factory_title' => self::NAME,
]);

$config['payum.api'] = function (ArrayObject $config): TpayApi {
Expand Down
4 changes: 4 additions & 0 deletions tests/Application/.env
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@ SYLIUS_MESSENGER_TRANSPORT_MAIN_FAILED_DSN=doctrine://default?queue_name=main_fa
SYLIUS_MESSENGER_TRANSPORT_CATALOG_PROMOTION_REMOVAL_DSN=doctrine://default?queue_name=catalog_promotion_removal
SYLIUS_MESSENGER_TRANSPORT_CATALOG_PROMOTION_REMOVAL_FAILED_DSN=doctrine://default?queue_name=catalog_promotion_removal_failed
###< symfony/messenger ###

###> payum/payum ###
PAYUM_CYPHER_KEY=<cypher>
###< payum/payum ###
2 changes: 2 additions & 0 deletions tests/Application/.env.test
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ SYLIUS_MESSENGER_TRANSPORT_MAIN_FAILED_DSN=sync://
SYLIUS_MESSENGER_TRANSPORT_CATALOG_PROMOTION_REMOVAL_DSN=sync://
SYLIUS_MESSENGER_TRANSPORT_CATALOG_PROMOTION_REMOVAL_FAILED_DSN=sync://
###< symfony/messenger ###

PAYUM_CYPHER_KEY=def00000626f0eff82cfc9ed8bd1f465a22a1ccd4d96e91585a6158975db8dda13c984280127fe8368d6cc7864851c3abc0af04e1fd2037086584e24d8abb6e39ae83583
1 change: 1 addition & 0 deletions tests/Application/.env.test_cached
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PAYUM_CYPHER_KEY=def00000626f0eff82cfc9ed8bd1f465a22a1ccd4d96e91585a6158975db8dda13c984280127fe8368d6cc7864851c3abc0af04e1fd2037086584e24d8abb6e39ae83583
55 changes: 55 additions & 0 deletions tests/Unit/Form/EventListener/DecryptGatewayConfigListenerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace Tests\CommerceWeavers\SyliusTpayPlugin\Unit\Form\EventListener;

use CommerceWeavers\SyliusTpayPlugin\Form\EventListener\DecryptGatewayConfigListener;
use CommerceWeavers\SyliusTpayPlugin\Form\EventListener\DecryptGatewayConfigListenerInterface;
use Payum\Core\GatewayInterface;
use Payum\Core\Model\GatewayConfigInterface;
use Payum\Core\Security\CryptedInterface;
use Payum\Core\Security\CypherInterface;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use Symfony\Component\Form\Event\PreSetDataEvent;
use Symfony\Component\Form\FormInterface;

final class DecryptGatewayConfigListenerTest extends TestCase
{
use ProphecyTrait;

private CypherInterface|ObjectProphecy $cypher;

protected function setUp(): void
{
$this->cypher = $this->prophesize(CypherInterface::class);
}

public function test_it_does_nothing_when_a_gateway_is_not_crypted(): void
{
$this->expectNotToPerformAssertions();

$form = $this->prophesize(FormInterface::class);
$gateway = $this->prophesize(GatewayInterface::class);

$this->createTestSubject()->__invoke(new PreSetDataEvent($form->reveal(), $gateway->reveal()));
}

public function test_it_decrypts_a_gateway_config(): void
{
$form = $this->prophesize(FormInterface::class);
$gateway = $this->prophesize(GatewayConfigInterface::class);
$gateway->willImplement(CryptedInterface::class);

$gateway->decrypt($this->cypher)->shouldBeCalled();

$this->createTestSubject()->__invoke(new PreSetDataEvent($form->reveal(), $gateway->reveal()));
}

private function createTestSubject(): DecryptGatewayConfigListenerInterface
{
return new DecryptGatewayConfigListener($this->cypher->reveal());
}
}
Loading

0 comments on commit 94aabff

Please sign in to comment.