Skip to content

Commit

Permalink
release v2.2.0 (#24)
Browse files Browse the repository at this point in the history
* read log file from the end
* use sandbox config from form in credential check
* save payment uuid to last_trans_id column in sales_order_payment

---------

Co-authored-by: Alexandru Lighezan <[email protected]>
  • Loading branch information
artyom-jaksov-tl and lighe authored Sep 2, 2024
1 parent 5db817a commit 5bc230d
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 30 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/setup-di-compile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
- uses: actions/checkout@v2

- name: Start Docker
run: PHP_VERSION=${{ matrix.PHP_VERSION }} MAGENTO_VERSION=magento${{ matrix.MAGENTO_VERSION }} docker-compose -f .github/workflows/templates/docker-compose.yml up -d
run: PHP_VERSION=${{ matrix.PHP_VERSION }} MAGENTO_VERSION=magento${{ matrix.MAGENTO_VERSION }} docker compose -f .github/workflows/templates/docker-compose.yml up -d

- name: Create branch for Composer and remove version from composer.json
run: git checkout -b continuous-integration-test-branch && sed -i '/version/d' ./composer.json
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [v2.2.0] - 2024-09-02

### Added

- Test validity of private key file when testing credentials.
- Store transaction ID in sales_order_payment table last_trans_id column.

### Fixed

- Check credentials button will correctly use unsaved values from the form fields.
- View logs button will show the end of the log file instead of the beginning.

## [v2.1.0] - 2024-07-17

### Added
Expand Down
56 changes: 47 additions & 9 deletions Controller/Adminhtml/Credentials/Check.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
use Magento\Framework\Filesystem\DirectoryList;
use Magento\Framework\Filesystem\Io\File;
use TrueLayer\Connect\Api\Config\RepositoryInterface as ConfigRepository;
use TrueLayer\Connect\Model\Config\Source\Mode;
use TrueLayer\Connect\Service\Client\ClientFactory;
use TrueLayer\Exceptions\ApiResponseUnsuccessfulException;
use TrueLayer\Exceptions\SignerException;
use TrueLayer\Interfaces\Client\ClientInterface;

/**
Expand Down Expand Up @@ -62,16 +65,43 @@ public function __construct(
*/
public function execute(): Json
{
$message = '';
try {
$this->testCredentials()->getMerchantAccounts();
$client = $this->testCredentials();
$client->getMerchantAccounts();
$client->getApiClient()->request()->uri('/test-signature')->post();

return $this->resultJson->setData(
['success' => true, 'msg' => __('Credentials correct!')->render()]
);
} catch (\Exception $exception) {
return $this->resultJson->setData(
['success' => false, 'msg' => 'Credentials are not correct']
);
} catch (ApiResponseUnsuccessfulException $e) {
if ($e->getMessage() === 'invalid_client') {
$message = __('Invalid Client Id or Secret');
}
elseif ($e->getMessage() === 'Unauthenticated') {
$message = __('Incorrect Private Key or KID');
}
} catch (SignerException $e) {
$m = $e->getMessage();
if (!!$m) {
if (strpos($m, 'is not private') !== false) {
$message = __('Private Key contains Public Key');
} elseif (strpos($m, 'Unable to load') !== false) {
$message = __('Malformed Private Key');
}
}
unset($m);
} catch (LocalizedException $e) {
$message = $e->getMessage();
} catch (\Exception $e) {
//leave default message
}
if (!$message) {
$message = __('Credentials are not correct.');
}
return $this->resultJson->setData(
['success' => false, 'msg' => $message]
);
}

/**
Expand All @@ -81,18 +111,26 @@ public function execute(): Json
private function testCredentials(): ?ClientInterface
{
$config = $this->getCredentials();
$mode = $this->getRequest()->getParam('mode');

if (!$config['credentials']['client_id']) {
throw new LocalizedException(__('No Client ID set!'));
throw new LocalizedException(__('Client Id is missing'));
}

if (!$config['credentials']['client_secret']) {
throw new LocalizedException(__('No Client Secret set!'));
throw new LocalizedException(__('Client Secret is missing'));
}

if (!$config['credentials']['private_key'] || $this->getRequest()->getParam('delete_private_key') === 'true') {
throw new LocalizedException(__('Private Key file is missing'));
}

$result = $this->clientFactory->create(
(int)$config['store_id'],
['credentials' => $config['credentials']]
[
'credentials' => $config['credentials'],
'force_sandbox' => $mode === Mode::SANDBOX,
]
);

$this->cleanSavedTemporaryPrivateKey();
Expand Down Expand Up @@ -122,7 +160,7 @@ private function getCredentials(): array
$keyId = $this->getRequest()->getParam('production_key_id');
}

$configCredentials = $this->configProvider->getCredentials($storeId);
$configCredentials = $this->configProvider->getCredentials($storeId, $mode === Mode::SANDBOX);
if ($clientSecret == '******') {
$clientSecret = $configCredentials['client_secret'];
}
Expand Down
91 changes: 75 additions & 16 deletions Controller/Adminhtml/Log/Stream.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class Stream extends Action implements HttpPostActionInterface
* Limit stream size to 100 lines
*/
public const MAX_LINES = 100;
private const READ_MAX_CHUNKS = 1024;
private const READ_BUFFER_SIZE = 1024;

private JsonFactory $resultJsonFactory;
private DirectoryList $dir;
Expand Down Expand Up @@ -110,25 +112,82 @@ private function isLogExists($logFilePath): bool
*/
private function prepareLogText($logFilePath): array
{
$size = $this->file->stat($logFilePath)['size'];
if (!$size) {
return [];
}
$file = $this->file->fileOpen($logFilePath, 'r');
$count = 0;

$result = [];
// phpcs:ignore Magento2.Functions.DiscouragedFunction
while (($line = fgets($file)) !== false && $count < self::MAX_LINES) {
$data = explode('] ', $line);
$date = ltrim(array_shift($data), '[');
$data = implode('] ', $data);
$data = explode(': ', $data);
array_shift($data);
$result[] = [
'date' => $date,
'msg' => implode(': ', $data)
];
$count++;
// we will start reading the file at the end
$position = $size;
$bufferSize = self::READ_BUFFER_SIZE;

$readCounter = 0;
$lineCounter = 0;

$logLines = [];
$logLine = '';
try {
do {
$position -= $bufferSize;
if ($position < 0) {
$bufferSize = $bufferSize + $position;
$position = 0;
}
$seek = fseek($file, $position);
if ($seek === -1) {
break;
}
$readBuffer = fread($file, $bufferSize);
$readCounter++;
$bufferLines = explode("\n", $readBuffer);
$lastLineKey = count($bufferLines) -1;
foreach (array_reverse($bufferLines) as $key => $bufferLine) {
$bufferLine = str_replace("\r", "", $bufferLine); //remove CR byte if present
$logLine = $bufferLine . $logLine;
if ('' !== $logLine // ignore empty lines
&& (
$key !== $lastLineKey // The last line may be incomplete, we need to keep prepending it until we hit another newline
|| ($key === $lastLineKey && $position === 0) // Unless we reached the beginning of the file
)
) {
$logLines[] = $this->formatLine($logLine);
$logLine = '';
$lineCounter++;
if ($lineCounter == self::MAX_LINES) {
break 2;
}
}
}
}
/**
* We have not collected MAX_LINES amount of lines yet but
* either we reached the beginning of the file
* or we have read READ_BUFFER_SIZE * READ_MAX_CHUNKS bytes from the file already.
*/
while ($position > 0 && $readCounter < self::READ_MAX_CHUNKS);
} catch (\Exception $e) {}
finally {
if ($file) {
$this->file->fileClose($file);
}
}

$this->file->fileClose($file);
return $result;
$logLines = array_reverse($logLines);

return $logLines;
}

private function formatLine($line) {
// phpcs:ignore Magento2.Functions.DiscouragedFunction
$data = explode('] ', $line);
$date = ltrim(array_shift($data), '[');
$data = implode('] ', $data);
$data = explode(': ', $data);
array_shift($data);
return [
'date' => $date,
'msg' => implode(': ', $data)
];
}
}
9 changes: 5 additions & 4 deletions Service/Client/ClientFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,11 @@ public function __construct(
*/
public function create(int $storeId = 0, ?array $data = []): ?ClientInterface
{
$credentials = $data['credentials'] ?? $this->configProvider->getCredentials($storeId);
$forceSandbox = $data['force_sandbox'] ?? null;
$credentials = $data['credentials'] ?? $this->configProvider->getCredentials($storeId, $forceSandbox);

try {
return $this->createClient($credentials);
return $this->createClient($credentials, $forceSandbox);
} catch (Exception $e) {
$this->logger->debug('Client Creation Failed', $e->getMessage());
throw $e;
Expand All @@ -56,15 +57,15 @@ public function create(int $storeId = 0, ?array $data = []): ?ClientInterface
* @return ClientInterface|null
* @throws SignerException
*/
private function createClient(array $credentials): ?ClientInterface
private function createClient(array $credentials, ?bool $forceSandbox = null): ?ClientInterface
{
Settings::tlAgent('truelayer-magento/' . $this->configProvider->getExtensionVersion());
return Client::configure()
->clientId($credentials['client_id'])
->clientSecret($credentials['client_secret'])
->keyId($credentials['key_id'])
->pemFile($credentials['private_key'])
->useProduction(!$this->configProvider->isSandbox())
->useProduction(is_null($forceSandbox) ? !$this->configProvider->isSandbox() : !$forceSandbox)
->create();
}
}
12 changes: 12 additions & 0 deletions Service/Order/PaymentUpdate/PaymentFailedService.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace TrueLayer\Connect\Service\Order\PaymentUpdate;

use Exception;
use Magento\Sales\Api\OrderPaymentRepositoryInterface;
use Magento\Sales\Api\OrderRepositoryInterface;
use TrueLayer\Connect\Api\Log\LogServiceInterface;
use TrueLayer\Connect\Api\Transaction\Payment\PaymentTransactionDataInterface;
Expand All @@ -16,20 +17,24 @@
class PaymentFailedService
{
private OrderRepositoryInterface $orderRepository;
private OrderPaymentRepositoryInterface $paymentRepository;
private PaymentTransactionService $transactionService;
private LogServiceInterface $logger;

/**
* @param OrderRepositoryInterface $orderRepository
* @param OrderPaymentRepositoryInterface $paymentRepository
* @param PaymentTransactionService $transactionService
* @param LogServiceInterface $logger
*/
public function __construct(
OrderRepositoryInterface $orderRepository,
OrderPaymentRepositoryInterface $paymentRepository,
PaymentTransactionService $transactionService,
LogServiceInterface $logger
) {
$this->orderRepository = $orderRepository;
$this->paymentRepository = $paymentRepository;
$this->transactionService = $transactionService;
$this->logger = $logger;
}
Expand Down Expand Up @@ -64,6 +69,13 @@ private function cancelOrder(PaymentTransactionDataInterface $transaction, strin
$this->logger->debug('Order cancelled');
}

// Update order payment
$payment = $order->getPayment();
$payment->setLastTransId($transaction->getPaymentUuid());
$payment->cancel();
$payment->setIsTransactionClosed(true);
$this->paymentRepository->save($payment);

$niceMessage = PaymentFailureReasonHelper::getHumanReadableLabel($failureReason);
$orderComment = "Order cancelled. $niceMessage ($failureReason)";
$order->addStatusToHistory($order->getStatus(), $orderComment, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ use TrueLayer\Connect\Block\Adminhtml\System\Config\Button\Credentials;
"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()) ?>', {
Expand Down

0 comments on commit 5bc230d

Please sign in to comment.