diff --git a/Api/Config/System/ConnectionInterface.php b/Api/Config/System/ConnectionInterface.php index 0877cef..7d46987 100644 --- a/Api/Config/System/ConnectionInterface.php +++ b/Api/Config/System/ConnectionInterface.php @@ -25,6 +25,7 @@ interface ConnectionInterface extends DebugInterface public const XML_PATH_PRODUCTION_CLIENT_SECRET = 'payment/truelayer/production_client_secret'; public const XML_PATH_PRODUCTION_PRIVATE_KEY = 'payment/truelayer/production_private_key'; public const XML_PATH_PRODUCTION_KEY_ID = 'payment/truelayer/production_key_id'; + public const XML_PATH_CACHE_ENCRYPTION_KEY = 'payment/truelayer/cache_encryption_key'; /** * Get Merchant Account Name diff --git a/Controller/Adminhtml/Credentials/Check.php b/Controller/Adminhtml/Credentials/Check.php index 2104045..f7cd6bc 100644 --- a/Controller/Adminhtml/Credentials/Check.php +++ b/Controller/Adminhtml/Credentials/Check.php @@ -171,7 +171,8 @@ private function getCredentials(): array 'client_id' => $clientId, 'client_secret' => $clientSecret, 'private_key' => $this->getPrivateKeyPath($configCredentials), - 'key_id' => $keyId + 'key_id' => $keyId, + 'cache_encryption_key' => $configCredentials['cache_encryption_key'] ] ]; } diff --git a/Model/Cache/CacheType.php b/Model/Cache/CacheType.php new file mode 100644 index 0000000..1656976 --- /dev/null +++ b/Model/Cache/CacheType.php @@ -0,0 +1,29 @@ +get(self::TYPE_IDENTIFIER), + self::CACHE_TAG + ); + } +} \ No newline at end of file diff --git a/Model/Config/System/ConnectionRepository.php b/Model/Config/System/ConnectionRepository.php index b39fc7c..b3459e3 100644 --- a/Model/Config/System/ConnectionRepository.php +++ b/Model/Config/System/ConnectionRepository.php @@ -42,7 +42,8 @@ public function getCredentials(?int $storeId = null, ?bool $forceSandbox = null) "client_id" => $this->getClientId($storeId, $isSandBox), "client_secret" => $this->getClientSecret($storeId, $isSandBox), "private_key" => $this->getPathToPrivateKey($storeId, $isSandBox), - "key_id" => $this->getKeyId($storeId, $isSandBox) + "key_id" => $this->getKeyId($storeId, $isSandBox), + "cache_encryption_key" => $this->getCacheEncryptionKey($storeId) ]; } @@ -103,4 +104,19 @@ private function getClientSecret(?int $storeId = null, bool $isSandBox = false): return ''; } + + /** + * @param int|null $storeId + * + * @return string + */ + private function getCacheEncryptionKey(?int $storeId = null): ?string + { + $path = self::XML_PATH_CACHE_ENCRYPTION_KEY; + $value = $this->getStoreValue($path, $storeId); + if (!empty($value)) { + return $this->encryptor->decrypt($value); + } + return null; + } } diff --git a/README.md b/README.md index dac7dbe..8c1f305 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,9 @@ bin/magento cache:flush bin/magento setup:static-content:deploy ``` 7. After the installation, go to your Magento® admin portal and open ‘Stores’ > ‘Configuration’ > ‘Sales’ > ‘TrueLayer’. +8. It's recommended that you also enable the cache for TrueLayer. There's two ways you can do this. + 1. In your Magento® admin portal open ‘System‘ > ‘Cache Management‘, click the checkbox for TrueLayer, select ‘Enable‘ from ‘Actions‘, and click ‘Submit‘. + 2. On your server running Magento® 2 run the following command from the command line: `bin/magento cache:enable truelayer` # Local development A basic docker-compose configuration is provided to make local development easier. To start it, run the following: diff --git a/Service/Cache/InvalidArgumentException.php b/Service/Cache/InvalidArgumentException.php new file mode 100644 index 0000000..b7c27c2 --- /dev/null +++ b/Service/Cache/InvalidArgumentException.php @@ -0,0 +1,11 @@ +cacheFrontend = $cacheFrontend; + } + + /** + * {@inheritdoc} + */ + public function get(string $key, mixed $default = null): mixed + { + $item = $this->cacheFrontend->load($key); + + if ($item === false) { + return $default; + } + + return unserialize($item); + } + + /** + * {@inheritdoc} + */ + public function set(string $key, $value, $ttl = null): bool + { + $value = serialize($value); + return $this->cacheFrontend->save( + $value, + $key, + [CacheType::CACHE_TAG], + $ttl + ); + } + + /** + * {@inheritdoc} + */ + public function delete($key): bool + { + return $this->cacheFrontend->remove($key); + } + + /** + * {@inheritdoc} + */ + public function clear(): bool + { + return $this->cacheFrontend->clean(); + } + + /** + * {@inheritdoc} + */ + public function getMultiple(iterable $keys, mixed $default = null): iterable + { + if ($keys instanceof \Traversable) { + $keys = iterator_to_array($keys, false); + } + $values = []; + foreach ($keys as $key) { + $values[$key] = $this->get($key, $default); + } + return $values; + } + + /** + * {@inheritdoc} + * @param iterable $values + */ + public function setMultiple(iterable $values, null|int|\DateInterval $ttl = null): bool + { + $stringKeyedValues = []; + foreach ($values as $key => $value) { + if (is_int($key)) { + $key = (string) $key; + } + + if (!is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given', gettype($key))); + } + + $stringKeyedValues[$key] = $value; + } + $success = true; + foreach ($stringKeyedValues as $key => $value) { + $success = $this->set($key, $value, $ttl) && $success; + } + + return $success; + } + + /** + * {@inheritdoc} + */ + public function deleteMultiple(iterable $keys): bool + { + if ($keys instanceof \Traversable) { + $keys = iterator_to_array($keys, false); + } + $success = true; + foreach ($keys as $key) { + $success = $this->delete($key) && $success; + } + return $success; + } + + /** + * {@inheritdoc} + */ + public function has(string $key): bool + { + return $this->cacheFrontend->test($key); + } +} \ No newline at end of file diff --git a/Service/Client/ClientFactory.php b/Service/Client/ClientFactory.php index 9cad06e..1fa6450 100644 --- a/Service/Client/ClientFactory.php +++ b/Service/Client/ClientFactory.php @@ -12,6 +12,7 @@ use TrueLayer\Client; use TrueLayer\Connect\Api\Config\RepositoryInterface as ConfigRepository; use TrueLayer\Connect\Api\Log\LogServiceInterface; +use TrueLayer\Connect\Service\Cache\Psr16CacheAdapter; use TrueLayer\Exceptions\InvalidArgumentException; use TrueLayer\Exceptions\SignerException; use TrueLayer\Interfaces\Client\ClientInterface; @@ -21,6 +22,7 @@ class ClientFactory { private ConfigRepository $configProvider; private LogServiceInterface $logger; + private Psr16CacheAdapter $cacheAdapter; /** * @param ConfigRepository $configProvider @@ -29,9 +31,11 @@ class ClientFactory public function __construct( ConfigRepository $configProvider, LogServiceInterface $logger, + Psr16CacheAdapter $cacheAdapter, ) { $this->configProvider = $configProvider; $this->logger = $logger; + $this->cacheAdapter = $cacheAdapter; } /** @@ -64,6 +68,8 @@ private function createClient(array $credentials, ?bool $forceSandbox = null): ? { Settings::tlAgent('truelayer-magento/' . $this->configProvider->getExtensionVersion()); + $cacheEncryptionKey = $credentials['cache_encryption_key']; + $clientFactory = Client::configure(); $clientFactory->clientId($credentials['client_id']) ->clientSecret($credentials['client_secret']) @@ -71,6 +77,10 @@ private function createClient(array $credentials, ?bool $forceSandbox = null): ? ->pemFile($credentials['private_key']) ->useProduction(is_null($forceSandbox) ? !$this->configProvider->isSandbox() : !$forceSandbox); + if ($cacheEncryptionKey) { + $clientFactory->cache($this->cacheAdapter, $cacheEncryptionKey); + } + return $clientFactory->create(); } } diff --git a/Setup/Recurring.php b/Setup/Recurring.php new file mode 100644 index 0000000..d585415 --- /dev/null +++ b/Setup/Recurring.php @@ -0,0 +1,30 @@ +startSetup(); + + $path = ConnectionInterface::XML_PATH_CACHE_ENCRYPTION_KEY; + $value = bin2hex(openssl_random_pseudo_bytes(32)); + $value = $this->encryptor->encrypt($value); + $this->resourceConfig->saveConfig($path, $value, 'default', 0); + + $setup->endSetup(); + } +} \ No newline at end of file diff --git a/composer.json b/composer.json index e6e5a67..8caec15 100644 --- a/composer.json +++ b/composer.json @@ -12,8 +12,8 @@ "magento/module-payment": ">=100.1.0", "magento/module-checkout": ">=100.1.0", "magento/module-sales": ">=100.1.0", - "truelayer/client": ">=1.2.0", - "php": ">=7.4.0" + "truelayer/client": ">=2.6.0", + "php": ">=8.1.0" }, "require-dev": { "phpstan/phpstan": "*", diff --git a/etc/cache.xml b/etc/cache.xml new file mode 100644 index 0000000..866b029 --- /dev/null +++ b/etc/cache.xml @@ -0,0 +1,7 @@ + + + + + Cache used by TrueLayer Plugin + + \ No newline at end of file