Skip to content

Commit d471d47

Browse files
authored
Merge branch 'release/v4.5.0' into release/v4.4.0
2 parents 6e1b248 + 60b08b6 commit d471d47

20 files changed

+1128
-413
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Payplug\Payments\Block\Adminhtml\Config;
6+
7+
use Magento\Backend\Block\Template\Context;
8+
use Magento\Config\Block\System\Config\Form\Field;
9+
use Magento\Config\Model\ResourceModel\Config\Data\CollectionFactory as ConfigDataCollectionFactory;
10+
use Magento\Framework\App\Config\ScopeConfigInterface;
11+
use Magento\Framework\View\Helper\SecureHtmlRenderer;
12+
use Magento\Store\Model\ScopeInterface as StoreScopeInterface;
13+
14+
abstract class AbstractOauth2 extends Field
15+
{
16+
public function __construct(
17+
private readonly ConfigDataCollectionFactory $configDatacollection,
18+
Context $context,
19+
array $data = [],
20+
?SecureHtmlRenderer $secureRenderer = null
21+
) {
22+
parent::__construct($context, $data, $secureRenderer);
23+
}
24+
25+
protected function _isInheritCheckboxRequired($element)
26+
{
27+
return false;
28+
}
29+
30+
protected function isEmailSetForCurrentScope(): bool
31+
{
32+
$websiteId = $this->getRequest()->getParam('website');
33+
34+
$scope = $websiteId ? StoreScopeInterface::SCOPE_WEBSITES : ScopeConfigInterface::SCOPE_TYPE_DEFAULT;
35+
$scopeId = $websiteId ?: 0;
36+
37+
$collection = $this->configDatacollection->create();
38+
$collection->addFieldToFilter('path', 'payplug_payments/oauth2/email')
39+
->addFieldToFilter('scope', $scope)
40+
->addFieldToFilter('scope_id', $scopeId);
41+
42+
return (bool)$collection->getSize();
43+
}
44+
}

Block/Adminhtml/Config/Login.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,22 @@ public function render(\Magento\Framework\Data\Form\Element\AbstractElement $ele
5151
$this->helper->initScopeData();
5252

5353
$connected = $this->helper->isConnected(ScopeInterface::SCOPE_WEBSITE, $this->request->getParam('website'));
54+
$oauthConnected = $this->helper->isOauthConnected(ScopeInterface::SCOPE_WEBSITE, $this->request->getParam('website'));
5455
$isVerified = $this->helper->getConfigValue('verified', ScopeInterface::SCOPE_WEBSITE, $this->request->getParam('website'));
5556

5657
$connexionFields = ['payplug_payments_general_email'];
58+
$connexionFieldsWithPwd = ['payplug_payments_general_email', 'payplug_payments_general_pwd'];
5759

5860
$disconnectionFields = ['payplug_payments_general_account_details'];
5961

6062
$elements = '';
6163
foreach ($element->getElements() as $field) {
64+
if ($oauthConnected) {
65+
if (in_array($field->getId(), $connexionFieldsWithPwd)) {
66+
continue;
67+
}
68+
}
69+
6270
if ($connected) {
6371
if (in_array($field->getId(), $connexionFields)) {
6472
continue;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Payplug\Payments\Block\Adminhtml\Config;
6+
7+
use Magento\Backend\Block\Widget\Button;
8+
use Magento\Framework\Data\Form\Element\AbstractElement;
9+
10+
class Oauth2Login extends AbstractOauth2
11+
{
12+
/** @noinspection PhpMissingParentCallCommonInspection */
13+
protected function _getElementHtml(AbstractElement $element): string
14+
{
15+
/** @var Button $buttonBlock */
16+
$buttonBlock = $this->getForm()->getLayout()->createBlock(Button::class);
17+
$websiteId = $this->getRequest()->getParam('website');
18+
$url = $this->getUrl('payplug_payments_admin/config/oauth2Login', ['website' => $websiteId]);
19+
20+
$data = [
21+
'label' => $websiteId ? __('Connect to Payplug for this website') : __('Connect to Payplug'),
22+
'onclick' => "setLocation('$url')",
23+
'class' => 'action-primary'
24+
];
25+
26+
return $buttonBlock->setData($data)->toHtml();
27+
}
28+
29+
public function render(AbstractElement $element): string
30+
{
31+
if ($this->isEmailSetForCurrentScope()) {
32+
return '';
33+
}
34+
35+
return parent::render($element);
36+
}
37+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Payplug\Payments\Block\Adminhtml\Config;
6+
7+
use Magento\Backend\Block\Template\Context;
8+
use Magento\Backend\Block\Widget\Button;
9+
use Magento\Config\Model\ResourceModel\Config\Data\CollectionFactory as ConfigDataCollectionFactory;
10+
use Magento\Framework\App\Config\ScopeConfigInterface;
11+
use Magento\Framework\App\State;
12+
use Magento\Framework\Data\Form\Element\AbstractElement;
13+
use Magento\Framework\Exception\LocalizedException;
14+
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
15+
use Magento\Framework\View\Helper\SecureHtmlRenderer;
16+
use Magento\Store\Model\ScopeInterface as StoreScopeInterface;
17+
use Payplug\Payments\Service\GetOauth2AccessTokenData;
18+
19+
class Oauth2Logout extends AbstractOauth2
20+
{
21+
public function __construct(
22+
private readonly ScopeConfigInterface $scopeConfig,
23+
private readonly GetOauth2AccessTokenData $getOauth2AccessTokenData,
24+
private readonly State $appState,
25+
private readonly TimezoneInterface $timezone,
26+
ConfigDataCollectionFactory $configDatacollection,
27+
Context $context,
28+
array $data = [],
29+
?SecureHtmlRenderer $secureRenderer = null
30+
) {
31+
parent::__construct(
32+
$configDatacollection,
33+
$context,
34+
$data,
35+
$secureRenderer
36+
);
37+
}
38+
39+
/** @noinspection PhpMissingParentCallCommonInspection */
40+
protected function _getElementHtml(AbstractElement $element): string
41+
{
42+
/** @var Button $buttonBlock */
43+
$buttonBlock = $this->getForm()->getLayout()->createBlock(Button::class);
44+
$websiteId = $this->getRequest()->getParam('website');
45+
$url = $this->getUrl('payplug_payments_admin/config/oauth2Logout', ['website' => $websiteId]);
46+
47+
$data = [
48+
'label' => __('Logout'),
49+
'onclick' => "setLocation('$url')"
50+
];
51+
52+
$statusLabel = __(
53+
'Your are currently authenticated with email <strong>%1</strong> (%2)',
54+
$this->getEmailValue(),
55+
$websiteId && $this->isEmailSetForCurrentScope() ? __('Website') : __('Default')
56+
);
57+
58+
$info = <<<HTML
59+
<div class="message message-success">{$statusLabel}</div>
60+
HTML;
61+
62+
if (!$this->isEmailSetForCurrentScope()) {
63+
return $info;
64+
}
65+
66+
if ($this->isDeveloperMode()) {
67+
$accessTokenData = $this->getAccessTokenData();
68+
$expirationDate = $this->timezone->date($accessTokenData['expires_date'])->format('Y-m-d H:i:s');
69+
$expirationLabel = __('Current access token expiration date');
70+
$tokenValueLabel = __('Current access token value');
71+
72+
$info .= <<<HTML
73+
<div class="message info">{$expirationLabel} : {$expirationDate}</div>
74+
<div class="message info">{$tokenValueLabel} : <code style="overflow-wrap: anywhere;">{$accessTokenData['access_token']}</code></div>
75+
HTML;
76+
}
77+
78+
return $info . '<br>' . $buttonBlock->setData($data)->toHtml();
79+
}
80+
81+
public function render(AbstractElement $element): string
82+
{
83+
if (!$this->getEmailValue()) {
84+
return '';
85+
}
86+
87+
return parent::render($element);
88+
}
89+
90+
private function getEmailValue(): ?string
91+
{
92+
93+
$websiteId = $this->getCurrentWebsite();
94+
95+
return $this->scopeConfig->getValue(
96+
'payplug_payments/oauth2/email',
97+
$websiteId ? StoreScopeInterface::SCOPE_WEBSITES : ScopeConfigInterface::SCOPE_TYPE_DEFAULT,
98+
$websiteId ?: 0
99+
);
100+
}
101+
102+
private function getAccessTokenData(): ?array
103+
{
104+
try {
105+
return $this->getOauth2AccessTokenData->execute($this->getCurrentWebsite());
106+
} catch (LocalizedException) {
107+
return null;
108+
}
109+
}
110+
111+
private function getCurrentWebsite(): ?int
112+
{
113+
return $this->getRequest()->getParam('website') ?: null;
114+
}
115+
116+
private function isDeveloperMode(): bool
117+
{
118+
return $this->appState->getMode() === State::MODE_DEVELOPER;
119+
}
120+
}

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [4.5.0](https://github.com/payplug/payplug-magento2/releases/tag/v4.5.0) - 2025-07-04
9+
10+
### Features
11+
12+
- Implement OAuth2 redirect handling and securely store credentials
13+
- Set up automatic JWT generation and refresh
14+
- Integrate OAuth2 login UI (button)
15+
- Sign every API request with the JWT token
16+
- Support both legacy authentication and OAuth2 (dual mode)
17+
18+
**[View diff](https://github.com/payplug/payplug-magento2/compare/v4.3.4...v4.5.0)**
19+
820
## [4.4.0](https://github.com/payplug/payplug-magento2/releases/tag/v4.4.0) - 2025-06-24
921

1022
### Features
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Payplug\Payments\Controller\Adminhtml\Config;
6+
7+
use Magento\Backend\App\Action;
8+
use Magento\Backend\App\Action\Context;
9+
use Magento\Backend\Model\Auth\Session as AdminAuthSession;
10+
use Magento\Framework\App\Action\HttpGetActionInterface;
11+
use Magento\Framework\App\RequestInterface;
12+
use Magento\Framework\Controller\Result\RedirectFactory;
13+
use Magento\Framework\UrlInterface;
14+
use Payplug\Authentication as PayplugAuthentication;
15+
use Payplug\Exception\ConfigurationException;
16+
use Payplug\Payments\Logger\Logger;
17+
18+
class Oauth2FetchAuthCode extends Action implements HttpGetActionInterface
19+
{
20+
public const OAUTH_CONFIG_FETCH_DATA = 'payplug_payments_admin/config/oauth2FetchClientData';
21+
public const OAUTH_CONFIG_FETCH_AUTH = 'payplug_payments_admin/config/oauth2FetchAuthCode';
22+
23+
public function __construct(
24+
private readonly RequestInterface $request,
25+
private readonly UrlInterface $urlBuilder,
26+
private readonly RedirectFactory $redirectFactory,
27+
private readonly Logger $logger,
28+
private readonly AdminAuthSession $adminAuthSession,
29+
Context $context
30+
) {
31+
parent::__construct($context);
32+
}
33+
34+
public function execute()
35+
{
36+
$clientId = $this->request->getParam('client_id');
37+
$companyId = $this->request->getParam('company_id');
38+
$websiteId = $this->request->getParam('website');
39+
$callbackUrl = $this->urlBuilder->getUrl(
40+
Oauth2FetchAuthCode::OAUTH_CONFIG_FETCH_DATA,
41+
['website' => $websiteId]
42+
);
43+
44+
$codeVerifier = bin2hex(openssl_random_pseudo_bytes(50));
45+
46+
$this->adminAuthSession->setData(Oauth2FetchClientData::PAYPLUG_OAUTH2_AUTHENTICATION_CONTEXT_DATA, [
47+
'client_id' => $clientId,
48+
'company_id' => $companyId,
49+
'code_verifier' => $codeVerifier,
50+
'callback_url' => $callbackUrl,
51+
]);
52+
53+
try {
54+
PayplugAuthentication::initiateOAuth($clientId, $callbackUrl, $codeVerifier);
55+
// phpcs:ignore Magento2.Security.LanguageConstruct.ExitUsage
56+
exit;
57+
} catch (ConfigurationException $e) {
58+
$this->logger->error($e->getMessage());
59+
$this->messageManager->addErrorMessage(__('Could not retrieve Auth Code from Payplug Portal'));
60+
61+
return $this->redirectFactory->create()->setPath(
62+
'adminhtml/system_config/edit',
63+
['section' => 'payplug_payments']
64+
);
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)