diff --git a/src/Action/Api/ApiRequestAction.php b/src/Action/Api/ApiRequestAction.php
index 2bae3b9..328cbc5 100644
--- a/src/Action/Api/ApiRequestAction.php
+++ b/src/Action/Api/ApiRequestAction.php
@@ -2,7 +2,9 @@
 
 namespace Ekyna\Component\Payum\Payzen\Action\Api;
 
+use Ekyna\Component\Payum\Payzen\Api\TransactionIdInterface;
 use Ekyna\Component\Payum\Payzen\Request\Request;
+use Exception;
 use Payum\Core\Bridge\Spl\ArrayObject;
 use Payum\Core\Exception\RequestNotSupportedException;
 use Payum\Core\Reply\HttpRedirect;
@@ -14,10 +16,21 @@
  */
 class ApiRequestAction extends AbstractApiAction
 {
+    /**
+     * @var TransactionIdInterface
+     */
+    private $transactionId;
+
+    public function __construct(
+        TransactionIdInterface $transactionId
+    ) {
+        $this->transactionId = $transactionId;
+    }
+
     /**
      * @inheritdoc
      *
-     * @throws HttpRedirect
+     * @throws HttpRedirect|Exception
      */
     public function execute($request): void
     {
@@ -30,12 +43,20 @@ public function execute($request): void
             return;
         }
 
-        $model['vads_trans_id'] = $this->api->getTransactionId();
-        // Current UTC date time
-        $model['vads_trans_date'] = (new \DateTime('now', new \DateTimeZone('UTC')))->format('YmdHis');
-
         $data = $model->getArrayCopy();
 
+        // If your application needs to generate a trans_id before this request
+        if (isset($data['generated_vads_trans_id']) && isset($data['generated_vads_trans_date'])) {
+            $data['vads_trans_id'] = $data['generated_vads_trans_id'];
+            $data['vads_trans_date'] = $data['generated_vads_trans_date'];
+            unset($data['generated_vads_trans_date']);
+            unset($data['generated_vads_trans_id']);
+        } else {
+            //You can generate a trans_id by a file method or a date method, you need to have the same date used to generate the trans_id
+            $transactionId = $this->transactionId->getTransactionId();
+            $data = array_merge($data, $transactionId);
+        }
+
         $this->logRequestData($data);
 
         $url = $this->api->createRequestUrl($data);
diff --git a/src/Action/ConvertPaymentAction.php b/src/Action/ConvertPaymentAction.php
index 7d905cf..3af4c48 100644
--- a/src/Action/ConvertPaymentAction.php
+++ b/src/Action/ConvertPaymentAction.php
@@ -36,24 +36,26 @@ public function execute($request)
 
         $model = ArrayObject::ensureArrayObject($payment->getDetails());
 
-        if (false == $model['vads_amount']) {
+        if (!$model['vads_amount']) {
             $this->gateway->execute($currency = new GetCurrency($payment->getCurrencyCode()));
             if (2 < $currency->exp) {
                 throw new RuntimeException('Unexpected currency exp.');
             }
-            $divisor = pow(10, 2 - $currency->exp);
+            // $currecy->exp is the number of decimal required with this currency
+            $multiplier = pow(10, $currency->exp);
 
             $model['vads_currency'] = (string)$currency->numeric;
-            $model['vads_amount'] = (string)abs($payment->getTotalAmount() / $divisor);
+            // used to send a non-decimal value to the platform, it can be reverted with currency->exp who be known by Payzen
+            $model['vads_amount'] = (string)abs($payment->getTotalAmount() * $multiplier);
         }
 
-        if (false == $model['vads_order_id']) {
+        if (!$model['vads_order_id']) {
             $model['vads_order_id'] = $payment->getNumber();
         }
-        if (false == $model['vads_cust_id']) {
+        if (!$model['vads_cust_id']) {
             $model['vads_cust_id'] = $payment->getClientId();
         }
-        if (false == $model['vads_cust_email']) {
+        if (!$model['vads_cust_email']) {
             $model['vads_cust_email'] = $payment->getClientEmail();
         }
 
diff --git a/src/Api/Api.php b/src/Api/Api.php
index 4e01666..56dd516 100644
--- a/src/Api/Api.php
+++ b/src/Api/Api.php
@@ -3,7 +3,6 @@
 namespace Ekyna\Component\Payum\Payzen\Api;
 
 use Payum\Core\Exception\LogicException;
-use Payum\Core\Exception\RuntimeException;
 use Symfony\Component\OptionsResolver\Options;
 use Symfony\Component\OptionsResolver\OptionsResolver;
 
@@ -54,55 +53,9 @@ public function setConfig(array $config)
             ->resolve($config);
     }
 
-    /**
-     * Returns the next transaction id.
-     *
-     * @return string
-     */
-    public function getTransactionId(): string
+    public function getConfig(): array
     {
-        $path = $this->getDirectoryPath() . 'transaction_id';
-
-        // Create file if not exists
-        if (!file_exists($path)) {
-            touch($path);
-            chmod($path, 0600);
-        }
-
-        $date = (new \DateTime())->format('Ymd');
-        $fileDate = date('Ymd', filemtime($path));
-        $isDailyFirstAccess = ($date != $fileDate);
-
-        // Open file
-        $handle = fopen($path, 'r+');
-        if (false === $handle) {
-            throw new RuntimeException('Failed to open the transaction ID file.');
-        }
-        // Lock File
-        if (!flock($handle, LOCK_EX)) {
-            throw new RuntimeException('Failed to lock the transaction ID file.');
-        }
-
-        $id = 1;
-        // If not daily first access, read and increment the id
-        if (!$isDailyFirstAccess) {
-            $id = (int)fread($handle, 6);
-            $id++;
-        }
-
-        // Truncate, write, unlock and close.
-        fseek($handle, 0);
-        ftruncate($handle, 0);
-        fwrite($handle, (string)$id);
-        fflush($handle);
-        flock($handle, LOCK_UN);
-        fclose($handle);
-
-        if ($this->config['debug']) {
-            $id += 89000;
-        }
-
-        return str_pad($id, 6, '0', STR_PAD_LEFT);
+        return $this->config;
     }
 
     /**
@@ -198,26 +151,6 @@ public function generateSignature(array $data, $hashed = true): string
         return $content;
     }
 
-    /**
-     * Returns the directory path and creates it if not exists.
-     *
-     * @return string
-     */
-    private function getDirectoryPath(): string
-    {
-        $path = $this->config['directory'];
-
-
-        // Create directory if not exists
-        if (!is_dir($path)) {
-            if (!mkdir($path, 0755, true)) {
-                throw new RuntimeException('Failed to create cache directory');
-            }
-        }
-
-        return $path . DIRECTORY_SEPARATOR;
-    }
-
     /**
      * Check that the API has been configured.
      *
diff --git a/src/Api/IdGeneratedByDate.php b/src/Api/IdGeneratedByDate.php
new file mode 100644
index 0000000..0231595
--- /dev/null
+++ b/src/Api/IdGeneratedByDate.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Ekyna\Component\Payum\Payzen\Api;
+
+use Exception;
+
+class IdGeneratedByDate implements TransactionIdInterface
+{
+    /**
+     * @throws Exception
+     */
+    public function getTransactionId(): array
+    {
+        $diff = (new \DateTimeImmutable('midnight', new \DateTimeZone('UTC')))
+            ->diff(new \DateTimeImmutable('now', new \DateTimeZone('UTC')));
+        return [
+            'vads_trans_date' => (new \DateTime('now', new \DateTimeZone('UTC')))->format('YmdHis'),
+            'vads_trans_id' => sprintf('%06d', random_int(0, 9) + (($diff->h * 3600 + $diff->i * 60 + $diff->s) * 10))
+        ];
+    }
+}
diff --git a/src/Api/IdGeneratedByFile.php b/src/Api/IdGeneratedByFile.php
new file mode 100644
index 0000000..6573231
--- /dev/null
+++ b/src/Api/IdGeneratedByFile.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Ekyna\Component\Payum\Payzen\Api;
+
+use Exception;
+use Payum\Core\Exception\RuntimeException;
+
+class IdGeneratedByFile implements TransactionIdInterface
+{
+    private $path;
+    private $debug;
+
+    public function __construct(
+        string $path,
+        bool $debug = false
+    ) {
+        $this->path = $path;
+        $this->debug = $debug;
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function getTransactionId(): array
+    {
+        $this->createDirectoryPath();
+        $this->path = rtrim($this->path,DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
+        $path = $this->path . 'transaction_id';
+
+        // Create file if not exists
+        if (!file_exists($path)) {
+            touch($path);
+            chmod($path, 0600);
+        }
+
+        $date = (new \DateTime())->format('Ymd');
+        $fileDate = date('Ymd', filemtime($path));
+        $isDailyFirstAccess = ($date != $fileDate);
+
+        // Open file
+        $handle = fopen($path, 'r+');
+        if (false === $handle) {
+            throw new RuntimeException('Failed to open the transaction ID file.');
+        }
+        // Lock File
+        if (!flock($handle, LOCK_EX)) {
+            throw new RuntimeException('Failed to lock the transaction ID file.');
+        }
+
+        $id = 1;
+        // If not daily first access, read and increment the id
+        if (!$isDailyFirstAccess) {
+            $id = (int)fread($handle, 6);
+            $id++;
+        }
+
+        // Truncate, write, unlock and close.
+        fseek($handle, 0);
+        ftruncate($handle, 0);
+        fwrite($handle, (string)$id);
+        fflush($handle);
+        flock($handle, LOCK_UN);
+        fclose($handle);
+
+        if ($this->debug) {
+            $id += 89000;
+        }
+
+        return [
+            'vads_trans_date' => (new \DateTime('now', new \DateTimeZone('UTC')))->format('YmdHis'),
+            'vads_trans_id' => str_pad($id, 6, '0', STR_PAD_LEFT)
+        ];
+    }
+
+    /**
+     * Returns the directory path and creates it if not exists.
+     */
+    public function createDirectoryPath(): void
+    {
+        // Create directory if not exists
+        if (!is_dir($this->path)) {
+            if (!mkdir($this->path, 0755, true)) {
+                throw new RuntimeException('Failed to create cache directory');
+            }
+        }
+    }
+}
diff --git a/src/Api/TransactionIdInterface.php b/src/Api/TransactionIdInterface.php
new file mode 100644
index 0000000..1ef68fa
--- /dev/null
+++ b/src/Api/TransactionIdInterface.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Ekyna\Component\Payum\Payzen\Api;
+
+interface TransactionIdInterface
+{
+    /** @return array{vads_trans_date: string, vads_trans_id: string} */
+    public function getTransactionId(): array;
+}
diff --git a/src/PayzenGatewayFactory.php b/src/PayzenGatewayFactory.php
index 534cb0e..2206464 100644
--- a/src/PayzenGatewayFactory.php
+++ b/src/PayzenGatewayFactory.php
@@ -2,6 +2,8 @@
 
 namespace Ekyna\Component\Payum\Payzen;
 
+use Ekyna\Component\Payum\Payzen\Api\IdGeneratedByFile;
+use Ekyna\Component\Payum\Payzen\Api\TransactionIdInterface;
 use Payum\Core\Bridge\Spl\ArrayObject;
 use Payum\Core\GatewayFactory;
 use Payum\Core\GatewayFactoryInterface;
@@ -13,17 +15,28 @@
  */
 class PayzenGatewayFactory extends GatewayFactory
 {
+    private $transactionId;
+
+    public function __construct(
+        array $defaultConfig = array(),
+        GatewayFactoryInterface $coreGatewayFactory = null,
+        TransactionIdInterface $transactionId = null
+    ) {
+        parent::__construct($defaultConfig, $coreGatewayFactory);
+        $this->transactionId = $transactionId ?? new IdGeneratedByFile($defaultConfig['directory'] ?? sys_get_temp_dir());
+    }
+
     /**
      * Builds a new factory.
      *
-     * @param array                        $defaultConfig
+     * @param array $defaultConfig
      * @param GatewayFactoryInterface|null $coreGatewayFactory
-     *
+     * @param TransactionIdInterface|null $transactionId
      * @return PayzenGatewayFactory
      */
-    public static function build(array $defaultConfig, GatewayFactoryInterface $coreGatewayFactory = null): PayzenGatewayFactory
+    public static function build(array $defaultConfig, GatewayFactoryInterface $coreGatewayFactory = null, TransactionIdInterface  $transactionId = null): PayzenGatewayFactory
     {
-        return new static($defaultConfig, $coreGatewayFactory);
+        return new static($defaultConfig, $coreGatewayFactory, $transactionId);
     }
 
     /**
@@ -42,7 +55,7 @@ protected function populateConfig(ArrayObject $config)
             'payum.action.refund'          => new Action\RefundAction(),
             'payum.action.status'          => new Action\StatusAction(),
             'payum.action.notify'          => new Action\NotifyAction(),
-            'payum.action.api.request'     => new Action\Api\ApiRequestAction(),
+            'payum.action.api.request'     => new Action\Api\ApiRequestAction($this->transactionId),
             'payum.action.api.response'    => new Action\Api\ApiResponseAction(),
         ]);
 
diff --git a/tests/Action/Api/AbstractApiActionTest.php b/tests/Action/Api/AbstractApiActionTest.php
index 925fa77..cf6ae70 100644
--- a/tests/Action/Api/AbstractApiActionTest.php
+++ b/tests/Action/Api/AbstractApiActionTest.php
@@ -6,6 +6,9 @@
 
 use Ekyna\Component\Payum\Payzen\Action\Api\AbstractApiAction;
 use Ekyna\Component\Payum\Payzen\Api\Api;
+use Ekyna\Component\Payum\Payzen\Api\IdGeneratedByDate;
+use Ekyna\Component\Payum\Payzen\Api\IdGeneratedByFile;
+use Ekyna\Component\Payum\Payzen\Api\TransactionIdInterface;
 use Ekyna\Component\Payum\Payzen\Tests\Action\AbstractActionTest;
 use PHPUnit\Framework\MockObject\MockObject;
 
@@ -21,9 +24,14 @@ abstract class AbstractApiActionTest extends AbstractActionTest
     /** @var MockObject|Api */
     protected $api;
 
+    /**
+     * @var MockObject|TransactionIdInterface
+     */
+    protected $transactionIdInterface;
+
     protected function setUp(): void
     {
-        $this->action = new $this->actionClass();
+        $this->action = new $this->actionClass($this->getIdGeneratedByFile());
         $this->action->setApi($this->getApiMock());
     }
 
@@ -36,7 +44,7 @@ protected function tearDown(): void
     /**
      * @return MockObject|Api
      */
-    protected function getApiMock(): MockObject
+    protected function getApiMock()
     {
         if ($this->api) {
             return $this->api;
@@ -44,4 +52,16 @@ protected function getApiMock(): MockObject
 
         return $this->api = $this->getMockBuilder(Api::class)->getMock();
     }
+
+    /**
+     * @return TransactionIdInterface
+     */
+    protected function getIdGeneratedByFile()
+    {
+        if ($this->transactionIdInterface) {
+            return $this->transactionIdInterface;
+        }
+
+        return $this->transactionIdInterface = new IdGeneratedByFile(dirname(__DIR__, 3) . '/cache/');
+    }
 }
diff --git a/tests/Action/Api/ApiRequestActionTest.php b/tests/Action/Api/ApiRequestActionTest.php
index 469235b..58c88f1 100644
--- a/tests/Action/Api/ApiRequestActionTest.php
+++ b/tests/Action/Api/ApiRequestActionTest.php
@@ -6,6 +6,8 @@
 
 use Ekyna\Component\Payum\Payzen\Action\Api\ApiRequestAction;
 use Ekyna\Component\Payum\Payzen\Request\Request;
+use Ekyna\Component\Payum\Payzen\Api\IdGeneratedByFile;
+use Ekyna\Component\Payum\Payzen\Api\IdGeneratedByDate;
 use Payum\Core\Reply\HttpResponse;
 
 /**
@@ -21,14 +23,15 @@ class ApiRequestActionTest extends AbstractApiActionTest
 
     /**
      * @test
+     * @throws \Exception
      */
     public function should_set_transaction_id_and_date_and_throw_redirect(): void
     {
         $api = $this->getApiMock();
-        $api
-            ->expects(static::once())
-            ->method('getTransactionId')
-            ->willReturn('000001');
+        $this->clearCache();
+        $transactionIdInterface = $this->getIdGeneratedByFile();
+        $transactionId = $transactionIdInterface->getTransactionId();
+        $this->assertEquals('000001', $transactionId['vads_trans_id']);
 
         $api
             ->expects(static::once())
@@ -41,4 +44,12 @@ public function should_set_transaction_id_and_date_and_throw_redirect(): void
 
         $this->action->execute($request);
     }
+
+    private function clearCache(): void
+    {
+        $path = dirname(__DIR__, 3) . '/cache/transaction_id';
+        if (file_exists($path)) {
+            unlink($path);
+        }
+    }
 }
diff --git a/tests/Action/ConvertPaymentActionTest.php b/tests/Action/ConvertPaymentActionTest.php
index 444abfd..f0b57e8 100644
--- a/tests/Action/ConvertPaymentActionTest.php
+++ b/tests/Action/ConvertPaymentActionTest.php
@@ -48,7 +48,7 @@ public function should_convert_payment_to_array(): void
 
         $result = $request->getResult();
 
-        $this->assertEquals('1234', $result['vads_amount']);
+        $this->assertEquals('123400', $result['vads_amount']);
         $this->assertEquals('978', $result['vads_currency']);
         $this->assertEquals('O01', $result['vads_order_id']);
         $this->assertEquals(123, $result['vads_cust_id']);
diff --git a/tests/Api/ApiTest.php b/tests/Api/ApiTest.php
index 6a9f01a..3161970 100644
--- a/tests/Api/ApiTest.php
+++ b/tests/Api/ApiTest.php
@@ -39,27 +39,6 @@ public function test_valid_config(): void
         $this->assertTrue(true);
     }
 
-    public function test_getTransactionId(): void
-    {
-        $this->clearCache();
-
-        $api = $this->createApi();
-
-        $id = $api->getTransactionId();
-        $this->assertEquals('000001', $id);
-
-        $id = $api->getTransactionId();
-        $this->assertEquals('000002', $id);
-
-        $id = $api->getTransactionId();
-        $this->assertEquals('000003', $id);
-
-        touch(__DIR__ . '/../../cache/transaction_id', time() - 60 * 60 * 24);
-
-        $id = $api->getTransactionId();
-        $this->assertEquals('000001', $id);
-    }
-
     /**
      * @param string $hashMode
      * @param array  $data
@@ -328,7 +307,7 @@ private function createApi(array $config = []): Api
             'site_id'     => '123456789',
             'certificate' => '987654321',
             'ctx_mode'    => Api::MODE_PRODUCTION,
-            'directory'   => __DIR__ . '/../../cache',
+            'directory'   => dirname(__DIR__, 2) . '/cache',
         ], $config));
 
         return $api;
@@ -336,7 +315,7 @@ private function createApi(array $config = []): Api
 
     private function clearCache(): void
     {
-        $path = __DIR__ . '/../../cache/transaction_id';
+        $path = dirname(__DIR__, 2) . '/cache/transaction_id';
         if (file_exists($path)) {
             unlink($path);
         }
diff --git a/tests/Api/IdGeneratedByDateTest.php b/tests/Api/IdGeneratedByDateTest.php
new file mode 100644
index 0000000..82dd19e
--- /dev/null
+++ b/tests/Api/IdGeneratedByDateTest.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Ekyna\Component\Payum\Payzen\Tests\Api;
+
+use Ekyna\Component\Payum\Payzen\Api\IdGeneratedByDate;
+use Ekyna\Component\Payum\Payzen\Api\TransactionIdInterface;
+use Ekyna\Component\Payum\Payzen\Tests\Assert\TransactionIdAssertTrait;
+use PHPUnit\Framework\TestCase;
+
+class IdGeneratedByDateTest extends TestCase
+{
+    use TransactionIdAssertTrait;
+
+    public function test_getTransactionId(): void
+    {
+        $transactionIdInterface = $this->createIdGeneratedByDate();
+
+        $data = $transactionIdInterface->getTransactionId();
+        $this->assertArrayHasVadsTransIdAndDateKeysTypedString($data);
+    }
+
+    /**
+     * Returns the TransactionIdInterface instance.
+     *
+     * @return TransactionIdInterface
+     */
+    private function createIdGeneratedByDate(): TransactionIdInterface
+    {
+        return new IdGeneratedByDate();
+    }
+}
diff --git a/tests/Api/IdGeneratedByFileTest.php b/tests/Api/IdGeneratedByFileTest.php
new file mode 100644
index 0000000..cd91025
--- /dev/null
+++ b/tests/Api/IdGeneratedByFileTest.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Ekyna\Component\Payum\Payzen\Tests\Api;
+
+use Ekyna\Component\Payum\Payzen\Api\IdGeneratedByFile;
+use Ekyna\Component\Payum\Payzen\Api\TransactionIdInterface;
+use Ekyna\Component\Payum\Payzen\Tests\Assert\TransactionIdAssertTrait;
+use PHPUnit\Framework\TestCase;
+
+class IdGeneratedByFileTest extends TestCase
+{
+    use TransactionIdAssertTrait;
+
+    public function test_getTransactionId(): void
+    {
+        $this->clearCache();
+
+        $transactionIdInterface = $this->createIdGeneratedByFile();
+
+        $data = $transactionIdInterface->getTransactionId();
+        $this->assertArrayHasVadsTransIdAndDateKeysTypedString($data);
+        $this->assertEquals('000001', $data['vads_trans_id']);
+
+        $data = $transactionIdInterface->getTransactionId();
+        $this->assertArrayHasVadsTransIdAndDateKeysTypedString($data);
+        $this->assertEquals('000002', $data['vads_trans_id']);
+
+        $data = $transactionIdInterface->getTransactionId();
+        $this->assertArrayHasVadsTransIdAndDateKeysTypedString($data);
+        $this->assertEquals('000003', $data['vads_trans_id']);
+
+        touch(dirname(__DIR__, 2) . '/cache/transaction_id', time() - 60 * 60 * 24);
+
+        $data = $transactionIdInterface->getTransactionId();
+        $this->assertArrayHasVadsTransIdAndDateKeysTypedString($data);
+        $this->assertEquals('000001', $data['vads_trans_id']);
+    }
+
+    /**
+     * Returns the TransactionIdInterface instance.
+     *
+     * @return TransactionIdInterface
+     */
+    private function createIdGeneratedByFile(): TransactionIdInterface
+    {
+        return new IdGeneratedByFile(dirname(__DIR__, 2) . '/cache/');
+    }
+
+    private function clearCache(): void
+    {
+        $path = dirname(__DIR__, 2) . '/cache/transaction_id';
+        if (file_exists($path)) {
+            unlink($path);
+        }
+    }
+}
diff --git a/tests/Assert/TransactionIdAssertTrait.php b/tests/Assert/TransactionIdAssertTrait.php
new file mode 100644
index 0000000..a412126
--- /dev/null
+++ b/tests/Assert/TransactionIdAssertTrait.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Ekyna\Component\Payum\Payzen\Tests\Assert;
+
+use Ekyna\Component\Payum\Payzen\Tests\Constraint\ArrayItem;
+use PHPUnit\Framework\Constraint\ArrayHasKey;
+use PHPUnit\Framework\Constraint\Constraint;
+use PHPUnit\Framework\Constraint\IsType;
+use PHPUnit\Framework\Constraint\LogicalAnd;
+use PHPUnit\Framework\Constraint\RegularExpression;
+
+trait TransactionIdAssertTrait
+{
+    abstract public static function assertThat($value, Constraint $constraint, string $message = ''): void;
+
+    protected function assertArrayHasVadsTransIdAndDateKeysTypedString($array, string $message = '')
+    {
+        $this->assertThat(
+            $array,
+            LogicalAnd::fromConstraints(
+                new ArrayHasKey('vads_trans_id'),
+                new ArrayHasKey('vads_trans_date'),
+                new ArrayItem('vads_trans_id', new IsType('string')),
+                new ArrayItem('vads_trans_date', new IsType('string')),
+                new ArrayItem('vads_trans_id', new RegularExpression('/\d{6}/')),
+                new ArrayItem('vads_trans_date', new RegularExpression('/\d{8}/'))
+            ),
+            $message
+        );
+    }
+}
diff --git a/tests/Constraint/ArrayItem.php b/tests/Constraint/ArrayItem.php
new file mode 100644
index 0000000..87bba7b
--- /dev/null
+++ b/tests/Constraint/ArrayItem.php
@@ -0,0 +1,97 @@
+<?php declare(strict_types=1);
+/*
+ * This file is part of PHPUnit.
+ *
+ * (c) Sebastian Bergmann <sebastian@phpunit.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Ekyna\Component\Payum\Payzen\Tests\Constraint;
+
+use ArrayAccess;
+use PHPUnit\Framework\Constraint\Constraint;
+use SebastianBergmann\RecursionContext\InvalidArgumentException;
+use function array_key_exists;
+use function is_array;
+
+/**
+ * Constraint that asserts that the array it is evaluated for has a given key and match a constraint type.
+ *
+ * Uses array_key_exists() to check if the key is found in the input array, if not found the evaluation fails.
+ *
+ * The array key and the constraint type are passed in the constructor.
+ */
+final class ArrayItem extends Constraint
+{
+    /**
+     * @var string
+     */
+    private $key;
+    /**
+     * @var Constraint
+     */
+    private $constraint;
+
+    /**
+     * @param string $key
+     * @param Constraint $constraint
+     */
+    public function __construct(string $key, Constraint $constraint)
+    {
+        $this->key = $key;
+        $this->constraint = $constraint;
+    }
+
+    /**
+     * Returns a string representation of the constraint.
+     *
+     * @throws InvalidArgumentException
+     */
+    public function toString(): string
+    {
+        return 'has the key ' . $this->exporter()->export($this->key) .  $this->constraint->toString();
+    }
+
+    /**
+     * Evaluates the constraint for parameter $other. Returns true if the
+     * constraint is met, false otherwise.
+     *
+     * @param mixed $other value or object to evaluate
+     */
+    protected function matches($other): bool
+    {
+        if (is_array($other)) {
+            if (!array_key_exists($this->key, $other)) {
+                return false;
+            }
+
+            return $this->constraint->matches($other[$this->key]);
+        }
+
+        if ($other instanceof ArrayAccess) {
+            if (!$other->offsetExists($this->key)) {
+                return false;
+            }
+
+            return $this->constraint->matches($other[$this->key]);
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns the description of the failure.
+     *
+     * The beginning of failure messages is "Failed asserting that" in most
+     * cases. This method should return the second part of that sentence.
+     *
+     * @param mixed $other evaluated value or object
+     *
+     * @throws InvalidArgumentException
+     */
+    protected function failureDescription($other): string
+    {
+        return 'an array ' . $this->toString();
+    }
+}
diff --git a/tests/PayzenGatewayFactoryTest.php b/tests/PayzenGatewayFactoryTest.php
index 1222935..d431970 100644
--- a/tests/PayzenGatewayFactoryTest.php
+++ b/tests/PayzenGatewayFactoryTest.php
@@ -38,7 +38,7 @@ public function test_create_gateway()
             'ctx_mode'    => Api::MODE_PRODUCTION,
             'site_id'     => '123456',
             'certificate' => '123456',
-            'directory'   => __DIR__ . '/../cache',
+            'directory'   => dirname(__DIR__, 1) . '/cache',
         ]);
 
         $this->assertInstanceOf('Payum\Core\Gateway', $gateway);