diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml
index 502b27e..c4fd362 100644
--- a/.github/workflows/e2e-tests.yml
+++ b/.github/workflows/e2e-tests.yml
@@ -51,8 +51,6 @@ jobs:
docker exec -i magento mysql magento << "EOF"
REPLACE INTO `admin_user` VALUES (1,'Example','Example','user@example.com','exampleuser','12b66e0132008acb13a9eb880f9ff4e7393b64b74722dcda435a03ec7e0dd780:TWLwiabBH2lIPcDkKLrhu37GsEBRpnSb:3_32_2_67108864','2024-09-23 01:02:08','2024-10-10 16:39:29','2024-10-10 16:39:29',1,0,1,NULL,NULL,NULL,'en_US',0,NULL,NULL);
REPLACE INTO `admin_analytics_usage_version_log` VALUES (1,'2.4.6');
- REPLACE INTO `core_config_data` VALUES (4,'default',0,'currency/options/base','GBP','2024-10-11 11:26:34'),(5,'default',0,'currency/options/default','GBP','2024-10-11 11:26:34'),(6,'default',0,'currency/options/allow','GBP','2024-10-11 11:26:34');
- REPLACE INTO `core_config_data` VALUES (18,'default',0,'admin/usage/enabled','0','2024-10-10 16:43:40'),(19,'default',0,'carriers/flatrate/active','0','2024-10-10 16:43:40'),(20,'default',0,'carriers/flatrate/handling_fee',NULL,'2024-10-10 16:43:40'),(21,'default',0,'carriers/flatrate/specificcountry',NULL,'2024-10-10 16:43:40'),(22,'default',0,'carriers/flatrate/showmethod','0','2024-10-10 16:43:40'),(23,'default',0,'carriers/flatrate/sort_order',NULL,'2024-10-10 16:43:40'),(24,'default',0,'carriers/freeshipping/active','1','2024-10-10 16:43:40'),(25,'default',0,'carriers/freeshipping/free_shipping_subtotal',NULL,'2024-10-10 16:43:40'),(26,'default',0,'carriers/freeshipping/specificcountry',NULL,'2024-10-10 16:43:40'),(27,'default',0,'carriers/freeshipping/showmethod','0','2024-10-10 16:43:40'),(28,'default',0,'carriers/freeshipping/sort_order',NULL,'2024-10-10 16:43:40'),(29,'default',0,'carriers/tablerate/handling_fee',NULL,'2024-10-10 16:43:40'),(30,'default',0,'carriers/tablerate/specificcountry',NULL,'2024-10-10 16:43:40'),(31,'default',0,'carriers/tablerate/showmethod','0','2024-10-10 16:43:40'),(32,'default',0,'carriers/tablerate/sort_order',NULL,'2024-10-10 16:43:40'),(33,'default',0,'carriers/ups/shipper_number',NULL,'2024-10-10 16:43:40'),(34,'default',0,'carriers/ups/handling_fee',NULL,'2024-10-10 16:43:40'),(35,'default',0,'carriers/ups/free_shipping_enable','0','2024-10-10 16:43:40'),(36,'default',0,'carriers/ups/specificcountry',NULL,'2024-10-10 16:43:40'),(37,'default',0,'carriers/ups/showmethod','0','2024-10-10 16:43:40'),(38,'default',0,'carriers/ups/debug','0','2024-10-10 16:43:40'),(39,'default',0,'carriers/ups/sort_order',NULL,'2024-10-10 16:43:40'),(40,'default',0,'carriers/usps/userid',NULL,'2024-10-10 16:43:40'),(41,'default',0,'carriers/usps/password',NULL,'2024-10-10 16:43:40'),(42,'default',0,'carriers/usps/handling_fee',NULL,'2024-10-10 16:43:40'),(43,'default',0,'carriers/usps/free_shipping_enable','0','2024-10-10 16:43:40'),(44,'default',0,'carriers/usps/specificcountry',NULL,'2024-10-10 16:43:40'),(45,'default',0,'carriers/usps/debug','0','2024-10-10 16:43:40'),(46,'default',0,'carriers/usps/showmethod','0','2024-10-10 16:43:40'),(47,'default',0,'carriers/usps/sort_order',NULL,'2024-10-10 16:43:40'),(48,'default',0,'carriers/fedex/account',NULL,'2024-10-10 16:43:40'),(49,'default',0,'carriers/fedex/meter_number',NULL,'2024-10-10 16:43:40'),(50,'default',0,'carriers/fedex/key',NULL,'2024-10-10 16:43:40'),(51,'default',0,'carriers/fedex/password',NULL,'2024-10-10 16:43:40'),(52,'default',0,'carriers/fedex/handling_fee',NULL,'2024-10-10 16:43:40'),(53,'default',0,'carriers/fedex/residence_delivery','0','2024-10-10 16:43:40'),(54,'default',0,'carriers/fedex/smartpost_hubid',NULL,'2024-10-10 16:43:40'),(55,'default',0,'carriers/fedex/free_shipping_enable','0','2024-10-10 16:43:40'),(56,'default',0,'carriers/fedex/specificcountry',NULL,'2024-10-10 16:43:40'),(57,'default',0,'carriers/fedex/debug','0','2024-10-10 16:43:40'),(58,'default',0,'carriers/fedex/showmethod','0','2024-10-10 16:43:40'),(59,'default',0,'carriers/fedex/sort_order',NULL,'2024-10-10 16:43:40'),(60,'default',0,'carriers/dhl/id',NULL,'2024-10-10 16:43:40'),(61,'default',0,'carriers/dhl/password',NULL,'2024-10-10 16:43:40'),(62,'default',0,'carriers/dhl/handling_fee',NULL,'2024-10-10 16:43:40'),(63,'default',0,'carriers/dhl/free_method_nondoc',NULL,'2024-10-10 16:43:40'),(64,'default',0,'carriers/dhl/free_shipping_enable','0','2024-10-10 16:43:40'),(65,'default',0,'carriers/dhl/specificcountry',NULL,'2024-10-10 16:43:40'),(66,'default',0,'carriers/dhl/showmethod','0','2024-10-10 16:43:40'),(67,'default',0,'carriers/dhl/debug','0','2024-10-10 16:43:40'),(68,'default',0,'carriers/dhl/sandbox_mode','0','2024-10-10 16:43:40'),(69,'default',0,'carriers/dhl/sort_order',NULL,'2024-10-10 16:43:40'),(70,'default',0,'payment/truelayer/active','1','2024-10-10 16:44:45'),(71,'default',0,'payment/truelayer/merchant_account_name',NULL,'2024-10-10 16:44:45'),(72,'default',0,'payment/truelayer/mode','sandbox','2024-10-10 16:44:45'),(73,'default',0,'payment/truelayer/sandbox_client_id','${{ secrets.TEST_CLIENT_ID }}','2024-10-10 16:44:45'),(74,'default',0,'payment/truelayer/sandbox_client_secret','','2024-10-10 16:44:45'),(75,'default',0,'payment/truelayer/sandbox_key_id','${{ secrets.TEST_KID }}','2024-10-10 16:44:45'),(76,'default',0,'payment/truelayer/sandbox_private_key','sandbox/default/private-key.pem','2024-10-10 16:44:45'),(77,'default',0,'payment/truelayer/specificcountry','GB,IE,ES,FR,DE,NL,LT','2024-10-10 16:44:45'),(78,'default',0,'payment/truelayer/send_order_email','0','2024-10-10 16:44:45'),(79,'default',0,'payment/truelayer/send_invoice_email','0','2024-10-10 16:44:45'),(80,'default',0,'payment/truelayer/banking_providers','retail','2024-10-10 16:44:45'),(81,'default',0,'payment/truelayer/payment_page_primary_color','#000000','2024-10-10 16:44:45'),(82,'default',0,'payment/truelayer/payment_page_secondary_color','#e53935','2024-10-10 16:44:45'),(83,'default',0,'payment/truelayer/payment_page_tertiary_color','#32329f','2024-10-10 16:44:45'),(84,'default',0,'payment/truelayer/logging','0','2024-10-10 16:44:45');
REPLACE INTO `catalog_product_entity` VALUES (1,4,'simple','test-product',0,0,'2024-10-10 16:41:56','2024-10-10 16:41:56');
REPLACE INTO `catalog_product_entity_decimal` VALUES (1,77,0,1,0.010000),(2,82,0,1,1.000000);
REPLACE INTO `catalog_product_entity_int` VALUES (1,97,0,1,1),(2,136,0,1,2),(3,99,0,1,4);
@@ -64,12 +62,43 @@ jobs:
REPLACE INTO `directory_currency_rate` VALUES ('EUR','EUR',1.000000000000),('EUR','USD',1.415000000000),('GBP','GBP',1.000000000000),('USD','EUR',0.706700000000),('USD','USD',1.000000000000);
REPLACE INTO `inventory_low_stock_notification_configuration` VALUES ('default','test-product',NULL);
REPLACE INTO `inventory_source_item` VALUES (1,'default','test-product',0.0000,0);
- REPLACE INTO `ui_bookmark` VALUES (1,1,'product_listing','default',1,'Default View','{\"views\":{\"default\":{\"label\":\"Default View\",\"index\":\"default\",\"editable\":false,\"data\":{\"filters\":{\"applied\":{\"placeholder\":true}},\"paging\":{\"pageSize\":20,\"current\":1,\"options\":{\"20\":{\"value\":20,\"label\":20},\"30\":{\"value\":30,\"label\":30},\"50\":{\"value\":50,\"label\":50},\"100\":{\"value\":100,\"label\":100},\"200\":{\"value\":200,\"label\":200}},\"value\":20},\"search\":{\"value\":\"\"},\"columns\":{\"entity_id\":{\"visible\":true,\"sorting\":\"asc\"},\"name\":{\"visible\":true,\"sorting\":false},\"sku\":{\"visible\":true,\"sorting\":false},\"price\":{\"visible\":true,\"sorting\":false},\"websites\":{\"visible\":true,\"sorting\":false},\"qty\":{\"visible\":true,\"sorting\":false},\"short_description\":{\"visible\":false,\"sorting\":false},\"special_price\":{\"visible\":false,\"sorting\":false},\"cost\":{\"visible\":false,\"sorting\":false},\"weight\":{\"visible\":false,\"sorting\":false},\"meta_title\":{\"visible\":false,\"sorting\":false},\"meta_keyword\":{\"visible\":false,\"sorting\":false},\"meta_description\":{\"visible\":false,\"sorting\":false},\"url_key\":{\"visible\":false,\"sorting\":false},\"msrp\":{\"visible\":false,\"sorting\":false},\"actions\":{\"visible\":true,\"sorting\":false},\"ids\":{\"visible\":true,\"sorting\":false},\"type_id\":{\"visible\":true,\"sorting\":false},\"attribute_set_id\":{\"visible\":true,\"sorting\":false},\"visibility\":{\"visible\":true,\"sorting\":false},\"status\":{\"visible\":true,\"sorting\":false},\"manufacturer\":{\"visible\":false,\"sorting\":false},\"color\":{\"visible\":false,\"sorting\":false},\"custom_design\":{\"visible\":false,\"sorting\":false},\"page_layout\":{\"visible\":false,\"sorting\":false},\"country_of_manufacture\":{\"visible\":false,\"sorting\":false},\"custom_layout\":{\"visible\":false,\"sorting\":false},\"gift_message_available\":{\"visible\":false,\"sorting\":false},\"tax_class_id\":{\"visible\":false,\"sorting\":false},\"salable_quantity\":{\"visible\":true,\"sorting\":false},\"thumbnail\":{\"visible\":true,\"sorting\":false},\"updated_at\":{\"visible\":true,\"sorting\":false},\"special_from_date\":{\"visible\":false,\"sorting\":false},\"special_to_date\":{\"visible\":false,\"sorting\":false},\"news_from_date\":{\"visible\":false,\"sorting\":false},\"news_to_date\":{\"visible\":false,\"sorting\":false},\"custom_design_from\":{\"visible\":false,\"sorting\":false},\"custom_design_to\":{\"visible\":false,\"sorting\":false}},\"displayMode\":\"grid\",\"positions\":{\"ids\":0,\"entity_id\":1,\"thumbnail\":2,\"name\":3,\"type_id\":4,\"attribute_set_id\":5,\"sku\":6,\"price\":7,\"qty\":8,\"salable_quantity\":9,\"visibility\":10,\"status\":11,\"websites\":12,\"short_description\":13,\"special_price\":14,\"special_from_date\":15,\"special_to_date\":16,\"cost\":17,\"weight\":18,\"manufacturer\":19,\"meta_title\":20,\"meta_keyword\":21,\"meta_description\":22,\"color\":23,\"news_from_date\":24,\"news_to_date\":25,\"custom_design\":26,\"custom_design_from\":27,\"custom_design_to\":28,\"page_layout\":29,\"country_of_manufacture\":30,\"custom_layout\":31,\"url_key\":32,\"msrp\":33,\"gift_message_available\":34,\"tax_class_id\":35,\"updated_at\":36,\"actions\":37}},\"value\":\"Default View\"}}}','2024-10-10 16:40:36','2024-10-10 16:40:36'),(2,1,'product_listing','current',0,NULL,'{\"current\":{\"filters\":{\"applied\":{\"placeholder\":true}},\"paging\":{\"pageSize\":20,\"current\":1,\"options\":{\"20\":{\"value\":20,\"label\":20},\"30\":{\"value\":30,\"label\":30},\"50\":{\"value\":50,\"label\":50},\"100\":{\"value\":100,\"label\":100},\"200\":{\"value\":200,\"label\":200}},\"value\":20},\"search\":{\"value\":\"\"},\"columns\":{\"entity_id\":{\"visible\":true,\"sorting\":\"asc\"},\"name\":{\"visible\":true,\"sorting\":false},\"sku\":{\"visible\":true,\"sorting\":false},\"price\":{\"visible\":true,\"sorting\":false},\"websites\":{\"visible\":true,\"sorting\":false},\"qty\":{\"visible\":true,\"sorting\":false},\"short_description\":{\"visible\":false,\"sorting\":false},\"special_price\":{\"visible\":false,\"sorting\":false},\"cost\":{\"visible\":false,\"sorting\":false},\"weight\":{\"visible\":false,\"sorting\":false},\"meta_title\":{\"visible\":false,\"sorting\":false},\"meta_keyword\":{\"visible\":false,\"sorting\":false},\"meta_description\":{\"visible\":false,\"sorting\":false},\"url_key\":{\"visible\":false,\"sorting\":false},\"msrp\":{\"visible\":false,\"sorting\":false},\"actions\":{\"visible\":true,\"sorting\":false},\"ids\":{\"visible\":true,\"sorting\":false},\"type_id\":{\"visible\":true,\"sorting\":false},\"attribute_set_id\":{\"visible\":true,\"sorting\":false},\"visibility\":{\"visible\":true,\"sorting\":false},\"status\":{\"visible\":true,\"sorting\":false},\"manufacturer\":{\"visible\":false,\"sorting\":false},\"color\":{\"visible\":false,\"sorting\":false},\"custom_design\":{\"visible\":false,\"sorting\":false},\"page_layout\":{\"visible\":false,\"sorting\":false},\"country_of_manufacture\":{\"visible\":false,\"sorting\":false},\"custom_layout\":{\"visible\":false,\"sorting\":false},\"gift_message_available\":{\"visible\":false,\"sorting\":false},\"tax_class_id\":{\"visible\":false,\"sorting\":false},\"salable_quantity\":{\"visible\":true,\"sorting\":false},\"thumbnail\":{\"visible\":true,\"sorting\":false},\"updated_at\":{\"visible\":true,\"sorting\":false},\"special_from_date\":{\"visible\":false,\"sorting\":false},\"special_to_date\":{\"visible\":false,\"sorting\":false},\"news_from_date\":{\"visible\":false,\"sorting\":false},\"news_to_date\":{\"visible\":false,\"sorting\":false},\"custom_design_from\":{\"visible\":false,\"sorting\":false},\"custom_design_to\":{\"visible\":false,\"sorting\":false}},\"displayMode\":\"grid\",\"positions\":{\"ids\":0,\"entity_id\":1,\"thumbnail\":2,\"name\":3,\"type_id\":4,\"attribute_set_id\":5,\"sku\":6,\"price\":7,\"qty\":8,\"salable_quantity\":9,\"visibility\":10,\"status\":11,\"websites\":12,\"short_description\":13,\"special_price\":14,\"special_from_date\":15,\"special_to_date\":16,\"cost\":17,\"weight\":18,\"manufacturer\":19,\"meta_title\":20,\"meta_keyword\":21,\"meta_description\":22,\"color\":23,\"news_from_date\":24,\"news_to_date\":25,\"custom_design\":26,\"custom_design_from\":27,\"custom_design_to\":28,\"page_layout\":29,\"country_of_manufacture\":30,\"custom_layout\":31,\"url_key\":32,\"msrp\":33,\"gift_message_available\":34,\"tax_class_id\":35,\"updated_at\":36,\"actions\":37}}}','2024-10-10 16:40:37','2024-10-10 16:40:37');
REPLACE INTO `url_rewrite` VALUES (5,'product',1,'test-product.html','catalog/product/view/id/1',0,1,NULL,1,NULL);
EOF
+ - name: Add magento config
+ run: |
+ docker exec magento bin/magento config:set admin/usage/enabled 0;
+ docker exec magento bin/magento config:set admin/security/use_form_key 0;
+ docker exec magento bin/magento config:set general/country/default GB;
+ docker exec magento bin/magento config:set currency/options/allow GBP,EUR;
+ docker exec magento bin/magento config:set currency/options/base GBP;
+ docker exec magento bin/magento config:set currency/options/default GBP;
+ docker exec magento bin/magento config:set currency/options/allow GBP;
+ docker exec magento bin/magento config:set carriers/flatrate/active 0;
+ docker exec magento bin/magento config:set carriers/freeshipping/active 1;
+ docker exec magento bin/magento config:set carriers/freeshipping/specificcountry "";
+
+ - name: Add truelayer config
+ run: |
+ docker exec magento bin/magento config:set payment/truelayer/active 1;
+ docker exec magento bin/magento config:set payment/truelayer/mode sandbox;
+ docker exec magento bin/magento config:set payment/truelayer/send_order_email 0;
+ docker exec magento bin/magento config:set payment/truelayer/send_invoice_email 0;
+ docker exec magento bin/magento config:set payment/truelayer/banking_providers retail;
+ docker exec magento bin/magento config:set payment/truelayer/logging 0;
+ docker exec magento bin/magento config:set payment/truelayer/send_order_email 0;
+ docker exec magento bin/magento config:set payment/truelayer/send_invoice_email 0;
+ docker exec magento bin/magento config:set payment/truelayer/payment_page_primary_color \#000000;
+ docker exec magento bin/magento config:set payment/truelayer/payment_page_secondary_color \#e53935;
+ docker exec magento bin/magento config:set payment/truelayer/payment_page_tertiary_color \#32329f;
+ docker exec magento bin/magento config:set payment/truelayer/specificcountry GB,IE,ES,FR,DE,NL,LT;
+ docker exec magento bin/magento config:set payment/truelayer/sandbox_client_id ${{ secrets.TEST_CLIENT_ID }};
+ docker exec magento bin/magento config:set payment/truelayer/sandbox_key_id ${{ secrets.TEST_KID }};
+
- name: Set encrypted client secret
- run: docker exec magento sh -c 'php bin/magento config:set payment/truelayer/sandbox_client_secret $(php -r "require \"vendor/autoload.php\"; require \"app/bootstrap.php\"; \$bootstrap = \Magento\Framework\App\Bootstrap::create(BP, \$_SERVER); \$bootstrap->createApplication(\Magento\Framework\App\Http::class); \$encryptor = \Magento\Framework\App\ObjectManager::getInstance()->get(\Magento\Framework\Encryption\EncryptorInterface::class); echo \$encryptor->encrypt(\"${{ secrets.TEST_CLIENT_SECRET }}\");")'
+ run: |
+ docker exec magento sh -c 'php bin/magento config:set payment/truelayer/sandbox_client_secret $(php -r "require \"vendor/autoload.php\"; require \"app/bootstrap.php\"; \$bootstrap = \Magento\Framework\App\Bootstrap::create(BP, \$_SERVER); \$bootstrap->createApplication(\Magento\Framework\App\Http::class); \$encryptor = \Magento\Framework\App\ObjectManager::getInstance()->get(\Magento\Framework\Encryption\EncryptorInterface::class); echo \$encryptor->encrypt(\"${{ secrets.TEST_CLIENT_SECRET }}\");")'
+ docker exec magento sh -c 'php bin/magento config:set payment/truelayer/sandbox_private_key $(php -r "require \"vendor/autoload.php\"; require \"app/bootstrap.php\"; \$bootstrap = \Magento\Framework\App\Bootstrap::create(BP, \$_SERVER); \$bootstrap->createApplication(\Magento\Framework\App\Http::class); \$encryptor = \Magento\Framework\App\ObjectManager::getInstance()->get(\Magento\Framework\Encryption\EncryptorInterface::class); echo \$encryptor->encrypt(\"${{ secrets.TEST_PEM }}\");")'
- name: Reindex products
run: docker exec magento php bin/magento indexer:reindex
@@ -84,4 +113,4 @@ jobs:
docker exec -w /data/extensions/magento2 magento npx playwright install
- name: Run tests
- run: docker exec --env PW_TEST_HTML_REPORT_OPEN=never -w /data/extensions/magento2 magento npx playwright test --project=chromium --reporter=line
\ No newline at end of file
+ run: docker exec --env PW_TEST_HTML_REPORT_OPEN=never -w /data/extensions/magento2 magento npx playwright test --project=chromium --reporter=line
diff --git a/Block/Adminhtml/System/Config/Field/Base64FileUpload.php b/Block/Adminhtml/System/Config/Field/Base64FileUpload.php
new file mode 100644
index 0000000..8531602
--- /dev/null
+++ b/Block/Adminhtml/System/Config/Field/Base64FileUpload.php
@@ -0,0 +1,46 @@
+getHtmlId();
+ $mode = $element->getData('field_config')['depends']['fields']['mode']['value'] ?? Mode::SANDBOX;
+ $fieldType = $element->getData('field_config')['type'] ?? 'text';
+ $displayValue = $element->getValue();
+ $disabled = $element->getDisabled();
+ if ($displayValue && $fieldType == 'obscure') {
+ $displayValue = '******';
+ }
+
+ $this->setData([
+ 'htmlTextInputId' => $htmlTextInputId,
+ 'mode' => $mode,
+ 'fieldType' => $fieldType,
+ 'displayValue' => $displayValue,
+ 'disabled' => $disabled,
+ ]);
+ return $this->_toHtml();
+ }
+}
diff --git a/Controller/Adminhtml/Credentials/Check.php b/Controller/Adminhtml/Credentials/Check.php
index f7cd6bc..2341d1e 100644
--- a/Controller/Adminhtml/Credentials/Check.php
+++ b/Controller/Adminhtml/Credentials/Check.php
@@ -154,52 +154,41 @@ private function getCredentials(): array
$clientId = $this->getRequest()->getParam('sandbox_client_id');
$clientSecret = $this->getRequest()->getParam('sandbox_client_secret');
$keyId = $this->getRequest()->getParam('sandbox_key_id');
+ $privateKey = $this->getRequest()->getParam('sandbox_private_key');
} else {
$clientId = $this->getRequest()->getParam('production_client_id');
$clientSecret = $this->getRequest()->getParam('production_client_secret');
$keyId = $this->getRequest()->getParam('production_key_id');
+ $privateKey = $this->getRequest()->getParam('production_private_key');
}
$configCredentials = $this->configProvider->getCredentials($storeId, $mode === Mode::SANDBOX);
if ($clientSecret == '******') {
$clientSecret = $configCredentials['client_secret'];
}
+ if ($privateKey == '******') {
+ $privateKey = $configCredentials['private_key'];
+ } else {
+ if ($privateKey) {
+ $decoded = base64_decode($privateKey, true);
+ if (@base64_encode($decoded) === $privateKey) {
+ $privateKey = $decoded;
+ }
+ }
+ }
return [
'store_id' => $storeId,
'credentials' => [
'client_id' => $clientId,
'client_secret' => $clientSecret,
- 'private_key' => $this->getPrivateKeyPath($configCredentials),
+ 'private_key' => $privateKey,
'key_id' => $keyId,
'cache_encryption_key' => $configCredentials['cache_encryption_key']
]
];
}
- /**
- * @param array $configCredentials
- * @return string
- * @throws FileSystemException
- */
- private function getPrivateKeyPath(array $configCredentials): string
- {
- if ($privateKey = $this->getRequest()->getParam('private_key')) {
- $path = $this->directoryList->getPath('var') . self::PEM_UPLOAD_FILE;
- $fileInfo = $this->file->getPathInfo($path);
-
- if (!$this->file->fileExists($fileInfo['dirname'])) {
- $this->file->mkdir($fileInfo['dirname']);
- }
-
- $this->file->write($path, $privateKey);
-
- return $path;
- }
-
- return $configCredentials['private_key'];
- }
-
/**
* @return void
* @throws FileSystemException
diff --git a/Model/Config/System/ConnectionRepository.php b/Model/Config/System/ConnectionRepository.php
index b3459e3..62be0ec 100644
--- a/Model/Config/System/ConnectionRepository.php
+++ b/Model/Config/System/ConnectionRepository.php
@@ -41,7 +41,7 @@ public function getCredentials(?int $storeId = null, ?bool $forceSandbox = null)
return [
"client_id" => $this->getClientId($storeId, $isSandBox),
"client_secret" => $this->getClientSecret($storeId, $isSandBox),
- "private_key" => $this->getPathToPrivateKey($storeId, $isSandBox),
+ "private_key" => $this->getPrivateKey($storeId, $isSandBox),
"key_id" => $this->getKeyId($storeId, $isSandBox),
"cache_encryption_key" => $this->getCacheEncryptionKey($storeId)
];
@@ -52,18 +52,14 @@ public function getCredentials(?int $storeId = null, ?bool $forceSandbox = null)
* @param bool $isSandBox
* @return string
*/
- private function getPathToPrivateKey(?int $storeId = null, bool $isSandBox = false): string
+ private function getPrivateKey(?int $storeId = null, bool $isSandBox = false): string
{
$path = $isSandBox ? self::XML_PATH_SANDBOX_PRIVATE_KEY : self::XML_PATH_PRODUCTION_PRIVATE_KEY;
- if (!$savedPrivateKey = $this->getStoreValue($path, $storeId)) {
- return '';
+ if ($value = $this->getStoreValue($path, $storeId)) {
+ return $this->encryptor->decrypt($value);
}
- try {
- return $this->directoryList->getPath('var') . '/truelayer/' . $savedPrivateKey;
- } catch (\Exception $exception) {
- return '';
- }
+ return '';
}
/**
diff --git a/Model/System/Config/Backend/PrivateKey.php b/Model/System/Config/Backend/PrivateKey.php
index d316b7a..27d95a8 100644
--- a/Model/System/Config/Backend/PrivateKey.php
+++ b/Model/System/Config/Backend/PrivateKey.php
@@ -7,178 +7,34 @@
namespace TrueLayer\Connect\Model\System\Config\Backend;
-use Magento\Framework\App\Cache\TypeListInterface;
-use Magento\Framework\App\Config\ScopeConfigInterface;
-use Magento\Framework\App\Config\Value;
-use Magento\Framework\Data\Collection\AbstractDb;
-use Magento\Framework\Exception\LocalizedException;
-use Magento\Framework\Filesystem;
-use Magento\Framework\Filesystem\Directory\ReadInterface;
-use Magento\Framework\Filesystem\Io\File;
-use Magento\Framework\Model\Context;
-use Magento\Framework\Model\ResourceModel\AbstractResource;
-use Magento\Framework\Registry;
-use TrueLayer\Connect\Api\Config\System\ConnectionInterface;
+use Magento\Config\Model\Config\Backend\Encrypted;
/**
- * Backend model for saving certificate file
+ * Backend model for saving certificate
*/
-class PrivateKey extends Value
+class PrivateKey extends Encrypted
{
- public const FILENAME = 'private-key.pem';
/**
- * @var File
- */
- private $file;
- /**
- * @var ReadInterface
- */
- private $tmpDirectory;
- /**
- * @var ReadInterface
- */
- private $varDirectory;
-
- /**
- * @param Context $context
- * @param Registry $registry
- * @param ScopeConfigInterface $config
- * @param TypeListInterface $cacheTypeList
- * @param Filesystem $filesystem
- * @param File $file
- * @param AbstractResource|null $resource
- * @param AbstractDb|null $resourceCollection
- * @param array $data
- */
- public function __construct(
- Context $context,
- Registry $registry,
- ScopeConfigInterface $config,
- TypeListInterface $cacheTypeList,
- Filesystem $filesystem,
- File $file,
- AbstractResource $resource = null,
- AbstractDb $resourceCollection = null,
- array $data = []
- ) {
- $this->file = $file;
- $this->tmpDirectory = $filesystem->getDirectoryRead('sys_tmp');
- $this->varDirectory = $filesystem->getDirectoryRead('var');
- parent::__construct($context, $registry, $config, $cacheTypeList, $resource, $resourceCollection, $data);
- }
-
- /**
- * Process additional data before save config.
+ * Decode and encrypt value before saving
*
- * @return $this
- * @throws LocalizedException
- */
- public function beforeSave(): self
- {
- $value = (array)$this->getValue();
- $sandbox = $this->getPath() === ConnectionInterface::XML_PATH_SANDBOX_PRIVATE_KEY;
- $directory = $this->getDirectory($sandbox);
-
- if (!empty($value['delete'])) {
- $this->deleteCertificateAndReset($this->isObjectNew() ? '' : $this->getOldValue());
- return $this;
- }
-
- $tmpName = $this->getTmpName($sandbox);
- $isUploading = (is_string($tmpName) && !empty($tmpName) && $this->tmpDirectory->isExist($tmpName));
-
- if (!$isUploading) {
- $this->setValue($this->isObjectNew() ? '' : $this->getOldValue());
- return $this;
- }
-
- if ($isUploading) {
- $tmpPath = $this->tmpDirectory->getAbsolutePath($tmpName);
- if (!$this->tmpDirectory->stat($tmpPath)['size']) {
- throw new LocalizedException(__('The TrueLayer certificate file is empty.'));
- }
-
- $destinationPath = $this->varDirectory->getAbsolutePath('truelayer/' . $directory);
-
- $filePath = $directory . self::FILENAME;
- $this->file->checkAndCreateFolder($destinationPath);
- $this->file->mv(
- $tmpPath,
- $this->varDirectory->getAbsolutePath('truelayer/' . $filePath)
- );
- $this->setValue($filePath);
- }
-
- return $this;
- }
-
- /**
- * Delete the cert file from disk when deleting the setting.
- *
- * @return $this
- */
- public function beforeDelete()
- {
- $returnValue = parent::beforeDelete();
- $filePath = $this->isObjectNew() ? '' : $this->getOldValue();
- if ($filePath) {
- $absolutePath = $this->varDirectory->getAbsolutePath('truelayer/' . $filePath);
- if ($this->file->fileExists($absolutePath)) {
- $this->file->rm($absolutePath);
- }
- }
- return $returnValue;
- }
-
- /**
- * Delete the cert file and unset the config value.
- *
- * @param string $filePath
* @return void
*/
- private function deleteCertificateAndReset(string $filePath): void
+ public function beforeSave()
{
- if (!empty($filePath)) {
- $absolutePath = $this->varDirectory->getAbsolutePath('truelayer/' . $filePath);
- if ($this->file->fileExists($absolutePath)) {
- $this->file->rm($absolutePath);
+ $this->_dataSaveAllowed = false;
+ $value = (string)$this->getValue();
+ // don't save value, if an obscured value was received. This indicates that data was not changed.
+ if (!preg_match('/^\*+$/', $value) && !empty($value)) {
+ $this->_dataSaveAllowed = true;
+ $decoded = base64_decode($value, true);
+ if (!$decoded || @base64_encode($decoded) !== $value) {
+ $decoded = '';
}
- }
-
- $this->setValue('');
- }
-
- /**
- * Returns the directory based on set scope.
- *
- * @param bool $sandbox
- * @return string
- */
- private function getDirectory(bool $sandbox): string
- {
- $mode = $sandbox ? 'sandbox' : 'production';
- return $this->getScope() !== 'default'
- ? sprintf('%s/%s/%s/', $mode, $this->getScope(), $this->getScopeId())
- : sprintf('%s/default/', $mode);
- }
-
- /**
- * Returns the path to the uploaded tmp_file based on set scope.
- *
- * @param bool $sandbox
- * @return string
- */
- private function getTmpName(bool $sandbox): ?string
- {
- $files = $_FILES;
- if (empty($files)) {
- return null;
- }
- try {
- $tmpName = $files['groups']['tmp_name']['general']['fields'][$sandbox ? 'sandbox_private_key' : 'production_private_key']['value'];
- return empty($tmpName) ? null : $tmpName;
- } catch (\Exception $e) {
- return null;
+ $encrypted = $decoded ? $this->_encryptor->encrypt($decoded) : null;
+ $this->setValue($encrypted);
+ } elseif (empty($value)) {
+ $this->setValue(null);
+ $this->_dataSaveAllowed = true;
}
}
}
diff --git a/Service/Client/ClientFactory.php b/Service/Client/ClientFactory.php
index 1fa6450..6a391af 100644
--- a/Service/Client/ClientFactory.php
+++ b/Service/Client/ClientFactory.php
@@ -74,7 +74,7 @@ private function createClient(array $credentials, ?bool $forceSandbox = null): ?
$clientFactory->clientId($credentials['client_id'])
->clientSecret($credentials['client_secret'])
->keyId($credentials['key_id'])
- ->pemFile($credentials['private_key'])
+ ->pem($credentials['private_key'])
->useProduction(is_null($forceSandbox) ? !$this->configProvider->isSandbox() : !$forceSandbox);
if ($cacheEncryptionKey) {
diff --git a/Setup/UpgradeData.php b/Setup/UpgradeData.php
new file mode 100644
index 0000000..c37fe9e
--- /dev/null
+++ b/Setup/UpgradeData.php
@@ -0,0 +1,69 @@
+getVersion();
+
+ if(version_compare($setupVersion, '1.0.0', '<=')) {
+ $this->encryptPrivateKeys();
+ }
+ }
+
+ private function encryptPrivateKeys()
+ {
+ $this->dataCollection->addFilter('path', ConnectionInterface::XML_PATH_SANDBOX_PRIVATE_KEY, 'or');
+ $this->dataCollection->addFilter('path', ConnectionInterface::XML_PATH_PRODUCTION_PRIVATE_KEY, 'or');
+ $this->dataCollection->loadWithFilter();
+ /** @var \Magento\Framework\App\Config\Value[] $configItems */
+ $configItems = $this->dataCollection->getItems();
+ $this->dataCollection->clear()->getSelect()->reset('where');
+
+ $configPaths = [ConnectionInterface::XML_PATH_PRODUCTION_PRIVATE_KEY,ConnectionInterface::XML_PATH_SANDBOX_PRIVATE_KEY];
+ $varDirectory = $this->filesystem->getDirectoryRead('var');
+ foreach ($configItems as $configItem) {
+ $configPath = $configItem->getPath();
+ if (!in_array($configPath, $configPaths)) {
+ continue;
+ }
+ $configValue = $configItem->getValue();
+ if (!$configValue) {
+ continue;
+ }
+ $isPath = str_starts_with($configValue, 'sandbox/') || str_starts_with($configValue, 'production/');
+ $absPath = $isPath ? $varDirectory->getAbsolutePath('truelayer/' . $configValue) : null;
+ $fileExists = $absPath ? $this->file->fileExists($absPath, true) : false;
+ if ($fileExists) {
+ $privateKey = $this->file->read($absPath, null);
+ $encryptedKey = $this->encryptor->encrypt($privateKey);
+ $configItem->setValue($encryptedKey);
+ $this->file->rm($absPath);
+ } else {
+ $configItem->setValue(null);
+ }
+ $this->dataResourceModel->save($configItem);
+ }
+ }
+}
diff --git a/etc/adminhtml/system/general.xml b/etc/adminhtml/system/general.xml
index d63387e..2e26e78 100644
--- a/etc/adminhtml/system/general.xml
+++ b/etc/adminhtml/system/general.xml
@@ -76,18 +76,22 @@
production
-
+
+
payment/truelayer/production_private_key
+ TrueLayer\Connect\Block\Adminhtml\System\Config\Field\Base64FileUpload
TrueLayer\Connect\Model\System\Config\Backend\PrivateKey
1
production
-
+
+
payment/truelayer/sandbox_private_key
+ TrueLayer\Connect\Block\Adminhtml\System\Config\Field\Base64FileUpload
TrueLayer\Connect\Model\System\Config\Backend\PrivateKey
1
diff --git a/etc/module.xml b/etc/module.xml
index 02fb4d7..a76b59b 100644
--- a/etc/module.xml
+++ b/etc/module.xml
@@ -7,7 +7,7 @@
-->
-
+
diff --git a/tests/e2e/fixtures/base-page.ts b/tests/e2e/fixtures/base-page.ts
index f3fa550..75511fb 100644
--- a/tests/e2e/fixtures/base-page.ts
+++ b/tests/e2e/fixtures/base-page.ts
@@ -4,7 +4,6 @@ import { ProductPage } from "../pages/product-page";
import { CheckoutPage } from "../pages/checkout-page";
import { HostedPaymentsPage } from "../pages/hosted-payments-page";
import { MockUkBankPage } from "../pages/mock-uk-bank-page";
-import { PaymentConfirmationPage } from "../pages/payment-confirmation-page";
import { MockUkBankAccountsPage } from "../pages/mock-uk-bank-accounts-page";
import { OrderConfirmationPage } from "../pages/order-confirmation-page";
@@ -14,7 +13,6 @@ export const test = base.extend<{
hostedPaymentsPage: HostedPaymentsPage;
mockUkBankPage: MockUkBankPage;
mockUkBankAccountsPage: MockUkBankAccountsPage;
- paymentConfirmationPage: PaymentConfirmationPage;
orderConfirmationPage: OrderConfirmationPage;
}>({
@@ -33,10 +31,7 @@ export const test = base.extend<{
mockUkBankAccountsPage: async ({ page }, use) => {
await use(new MockUkBankAccountsPage(page));
},
- paymentConfirmationPage: async ({ page }, use) => {
- await use(new PaymentConfirmationPage(page));
- },
orderConfirmationPage: async ({ page }, use) => {
await use(new OrderConfirmationPage(page));
},
-});
\ No newline at end of file
+});
diff --git a/tests/e2e/magento-e2e-tests.spec.ts b/tests/e2e/magento-e2e-tests-no-widget.spec.ts
similarity index 90%
rename from tests/e2e/magento-e2e-tests.spec.ts
rename to tests/e2e/magento-e2e-tests-no-widget.spec.ts
index 80614d6..ed6a461 100644
--- a/tests/e2e/magento-e2e-tests.spec.ts
+++ b/tests/e2e/magento-e2e-tests-no-widget.spec.ts
@@ -9,14 +9,13 @@ test.describe('Truelayer magento plugin E2E Tests', () => {
hostedPaymentsPage,
mockUkBankPage,
mockUkBankAccountsPage,
- paymentConfirmationPage,
orderConfirmationPage,
}) => {
// arrange
await productPage.navigateTo();
await productPage.addToCart();
await checkoutPage.navigateToShippingStep();
- await checkoutPage.fillShippingDetailsAndSubmit('truelayer@example.com');
+ await checkoutPage.fillShippingDetailsAndSubmit('truelayer@example.com', isMobile);
await checkoutPage.clickPaymentMethod();
await checkoutPage.clickPlaceOrderButton();
@@ -28,7 +27,6 @@ test.describe('Truelayer magento plugin E2E Tests', () => {
}
await mockUkBankPage.enterOnlineBankingDetailsAndContinue();
await mockUkBankAccountsPage.selectAccountAndContinue();
- // await paymentConfirmationPage.waitForProcessingAndContinue();
await orderConfirmationPage.waitForProcessingAndReturnToStore();
})
});
diff --git a/tests/e2e/pages/checkout-page.ts b/tests/e2e/pages/checkout-page.ts
index 2430f2f..9db2317 100644
--- a/tests/e2e/pages/checkout-page.ts
+++ b/tests/e2e/pages/checkout-page.ts
@@ -24,7 +24,7 @@ export class CheckoutPage {
// Methods
- async fillShippingDetailsAndSubmit(email: string) {
+ async fillShippingDetailsAndSubmit(email: string, isMobile: boolean = false) {
await this.emailField().isVisible();
await this.emailField().fill(email);
await this.firstNameField().isVisible();
@@ -41,6 +41,9 @@ export class CheckoutPage {
await this.addressField().fill('10 Downing Street')
await this.postcodeField().isVisible();
await this.postcodeField().fill('SW1A 2AB');
+ if (isMobile) {
+ await this.page.locator('body').click({position: {x: 0, y: 0}});
+ }
await this.submitShippingInfoAndWaitForPageLoad();
}
@@ -52,11 +55,9 @@ export class CheckoutPage {
}
async submitShippingInfoAndWaitForPageLoad() {
- await this.nextStepButton().isEnabled();
+ await expect(this.nextStepButton()).toBeEnabled({timeout: 5000});
await this.nextStepButton().click();
- await this.page.waitForSelector('.loading-mask', { state: 'visible' });
- await this.page.waitForSelector('.loading-mask', { state: 'hidden' });
- await this.page.waitForSelector(this.paymentMethodSelector, { state: 'visible' });
+ await this.page.waitForSelector(this.paymentMethodSelector, { state: 'visible', timeout: 15000 });
}
async clickPaymentMethod() {
@@ -72,4 +73,4 @@ export class CheckoutPage {
await this.placeOrderButton().isEnabled();
await this.placeOrderButton().click();
}
-}
\ No newline at end of file
+}
diff --git a/tests/e2e/pages/hosted-payments-page.ts b/tests/e2e/pages/hosted-payments-page.ts
index e3655be..589b5f1 100644
--- a/tests/e2e/pages/hosted-payments-page.ts
+++ b/tests/e2e/pages/hosted-payments-page.ts
@@ -1,4 +1,4 @@
-import { Page } from '@playwright/test';
+import { expect, Page } from '@playwright/test';
export class HostedPaymentsPage {
page: Page;
@@ -8,14 +8,14 @@ export class HostedPaymentsPage {
}
// Locators
- mockBank = () => this.page.getByLabel('Select Mock UK Payments - Redirect Flow', { exact: true });
- continueButton = () => this.page.getByTestId('confirm-redirect-button');
- continueOnDesktopButton = () => this.page.getByTestId('continue-desktop');
+ mockBank = () => this.page.getByText('Mock UK Payments - Redirect Flow', { exact: true });
+ continueButton = () => this.page.getByTestId('go-to-bank-button');
+ continueOnDesktopButton = () => this.page.getByText('on this device');
// Methods
async selectMockBankAndContinueOnDesktop() {
await this.selectMockBankAndContinue();
- await this.continueOnDesktopButton().isVisible()
+ await expect(this.continueOnDesktopButton()).toBeVisible({timeout: 10000})
await this.continueOnDesktopButton().click();
}
@@ -24,9 +24,9 @@ export class HostedPaymentsPage {
}
private async selectMockBankAndContinue(){
- await this.mockBank().isVisible();
+ await expect(this.mockBank()).toBeVisible({timeout: 10000})
await this.mockBank().click();
- await this.continueButton().isVisible();
+ await expect(this.continueButton()).toBeVisible({timeout: 10000})
await this.continueButton().click();
}
-}
\ No newline at end of file
+}
diff --git a/tests/e2e/pages/order-confirmation-page.ts b/tests/e2e/pages/order-confirmation-page.ts
index 5f1bfa5..ff86c40 100644
--- a/tests/e2e/pages/order-confirmation-page.ts
+++ b/tests/e2e/pages/order-confirmation-page.ts
@@ -14,6 +14,10 @@ export class OrderConfirmationPage {
// Methods
async waitForProcessingAndReturnToStore() {
await this.paymentBeingProcessedText().isVisible();
- await expect(this.orderConfirmedText()).toBeVisible({timeout: 90000});
+ await this.expectOrderConfirmed(90000);
}
-}
\ No newline at end of file
+
+ async expectOrderConfirmed(timeout: number) {
+ await expect(this.orderConfirmedText()).toBeVisible({timeout});
+ }
+}
diff --git a/tests/e2e/pages/payment-confirmation-page.ts b/tests/e2e/pages/payment-confirmation-page.ts
deleted file mode 100644
index 521c34f..0000000
--- a/tests/e2e/pages/payment-confirmation-page.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { expect, Page } from '@playwright/test';
-
-export class PaymentConfirmationPage {
- page: Page;
-
- constructor(page: Page) {
- this.page = page;
- }
-
- // Locators
- paymentProcessingText = () => this.page.getByText('In progress');
- paymentConfirmedText = () => this.page.getByText('All done');
- continueButton = () => this.page.getByRole('button', {name: 'continue'})
-
- // Methods
- async waitForProcessingAndContinue() {
- await expect(this.paymentProcessingText().or(this.paymentConfirmedText())).toBeVisible({timeout: 40000})
- await expect(this.continueButton()).toBeVisible();
- await this.continueButton().click();
- }
-}
\ No newline at end of file
diff --git a/tests/e2e/pages/product-page.ts b/tests/e2e/pages/product-page.ts
index 43dfc61..390a6ce 100644
--- a/tests/e2e/pages/product-page.ts
+++ b/tests/e2e/pages/product-page.ts
@@ -17,11 +17,11 @@ export class ProductPage {
await this.addToCartButton().click();
const cartCounter = this.cartCounter()
await cartCounter.isVisible();
- await expect(cartCounter).toHaveText('1', {timeout: 1000})
+ await expect(cartCounter).toHaveText('1', {timeout: 5000})
}
async navigateTo(){
const url = `${process.env.E2E_TEST_URL as string}/catalog/product/view/id/1/s/test-product/category/2/`;
await this.page.goto(url);
}
-}
\ No newline at end of file
+}
diff --git a/view/adminhtml/templates/system/config/button/base64-file-upload.phtml b/view/adminhtml/templates/system/config/button/base64-file-upload.phtml
new file mode 100644
index 0000000..68f3353
--- /dev/null
+++ b/view/adminhtml/templates/system/config/button/base64-file-upload.phtml
@@ -0,0 +1,56 @@
+getData('mode');
+$htmlClass = 'truelayer_upload-file-as-string_'.$mode;
+$scope = 'truelayer_private-key-upload_'.$mode;
+$fieldType = $block->getData('fieldType');
+$displayValue = $block->getData('displayValue');
+$disabled = $block->getData('disabled');
+$disabledHtml = $disabled ? 'disabled="disabled"' : "";
+
+$htmlTextInputId = $block->getData('htmlTextInputId');
+$htmlFileInputId = $htmlTextInputId.'_file';
+?>
+
+
+
+ />
+
+
+
+
+
+
diff --git a/view/adminhtml/templates/system/config/button/credentials.phtml b/view/adminhtml/templates/system/config/button/credentials.phtml
index 224257e..d5c97f6 100644
--- a/view/adminhtml/templates/system/config/button/credentials.phtml
+++ b/view/adminhtml/templates/system/config/button/credentials.phtml
@@ -22,16 +22,16 @@ use TrueLayer\Connect\Block\Adminhtml\System\Config\Button\Credentials;
document.querySelector('#truelayer_general').addEventListener('change', (e) => {
// Check mode
- if (e.target.getAttribute('name').includes('[mode]')) {
+ if (e.target.getAttribute('name')?.includes('[mode]')) {
truelayer_mode = e.target.value;
}
if (e.target.getAttribute('type') === 'file') {
const FR = new FileReader();
- FR.onload = () => {
- truelayer_mode === 'sandbox'
- ? private_key_sandbox = FR.result
+ FR.onload = () => {
+ truelayer_mode === 'sandbox'
+ ? private_key_sandbox = FR.result
: private_key_production = FR.result;
}
@@ -46,19 +46,20 @@ use TrueLayer\Connect\Block\Adminhtml\System\Config\Button\Credentials;
jQuery("input[name='groups[general][fields][production_client_id][value]']").val(),
"production_client_secret":
jQuery("input[name='groups[general][fields][production_client_secret][value]']").val(),
+ "production_private_key":
+ jQuery("input[name='groups[general][fields][production_private_key][value]']").val(),
"production_key_id":
jQuery("input[name='groups[general][fields][production_key_id][value]']").val(),
"sandbox_client_id":
jQuery("input[name='groups[general][fields][sandbox_client_id][value]']").val(),
"sandbox_client_secret":
jQuery("input[name='groups[general][fields][sandbox_client_secret][value]']").val(),
+ "sandbox_private_key":
+ jQuery("input[name='groups[general][fields][sandbox_private_key][value]']").val(),
"sandbox_key_id":
jQuery("input[name='groups[general][fields][sandbox_key_id][value]']").val(),
"mode":
jQuery("select[name='groups[general][fields][mode][value]']").val(),
- "private_key": truelayer_mode === 'sandbox' ? private_key_sandbox : private_key_production,
- "delete_private_key":
- jQuery("input[name='groups[general][fields][sandbox_private_key][value][delete]']").is(':checked'),
};
new Ajax.Request('= $block->escapeUrl($block->getApiCheckUrl()) ?>', {
diff --git a/view/adminhtml/web/js/handle-upload.js b/view/adminhtml/web/js/handle-upload.js
new file mode 100644
index 0000000..94763a9
--- /dev/null
+++ b/view/adminhtml/web/js/handle-upload.js
@@ -0,0 +1,35 @@
+/**
+ * Copyright © TrueLayer Ltd, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+define([
+ 'uiComponent',
+], function (Component) {
+ 'use strict';
+
+ return Component.extend({
+ clickUpload() {
+ document.getElementById(this.htmlFileInputId).click();
+ },
+ handleFile(object, event) {
+ const textInput = document.getElementById(this.htmlTextInputId);
+ // Get a reference to the file
+ const file = event?.target?.files[0];
+
+ if (!file) {
+ return;
+ }
+ // Encode the file using the FileReader API
+ const reader = new FileReader();
+ reader.onloadend = () => {
+ // Use a regex to remove data url part
+ const base64String = reader.result
+ .replace(/^data:.+,/, "");
+
+ textInput.value = base64String;
+ };
+ reader.readAsDataURL(file);
+ }
+ });
+});