From 38f906b003a3881798ca1e457c3ba05cc7cab3a4 Mon Sep 17 00:00:00 2001 From: Gytautas Zumaras Date: Mon, 16 Mar 2026 11:41:26 +0200 Subject: [PATCH 1/2] fields settings licence block logic and licence fetching --- ...dminSaferPayOfficialSettingsController.php | 28 ++++- src/Api/Request/GetLicenseService.php | 76 +++++++++++++ .../Request/GetLicense/GetLicenseRequest.php | 62 ++++++++++ src/Service/SaferPayGetLicense.php | 106 ++++++++++++++++++ src/Service/SettingsTranslationService.php | 2 - .../components/settings/api-credentials.tsx | 44 +++----- 6 files changed, 285 insertions(+), 33 deletions(-) create mode 100644 src/Api/Request/GetLicenseService.php create mode 100644 src/DTO/Request/GetLicense/GetLicenseRequest.php create mode 100644 src/Service/SaferPayGetLicense.php diff --git a/controllers/admin/AdminSaferPayOfficialSettingsController.php b/controllers/admin/AdminSaferPayOfficialSettingsController.php index dfce1b08..4e3fa993 100755 --- a/controllers/admin/AdminSaferPayOfficialSettingsController.php +++ b/controllers/admin/AdminSaferPayOfficialSettingsController.php @@ -233,7 +233,7 @@ public function ajaxProcessSaveCredentials() $this->ajaxResponse( true, - $this->module->l('API Credentials saved successfully', self::FILE_NAME) . $licenseMessage, + $this->module->l('Settings saved successfully.', self::FILE_NAME) . $licenseMessage, [ 'hasBusinessLicense' => $hasBusinessLicense, ] @@ -445,6 +445,32 @@ private function collectSettingsData() /** @var SaferPayConfiguration $configuration */ $configuration = $this->module->getService(SaferPayConfiguration::class); + // Re-fetch license from Saferpay Management API on every page load + $isTestMode = (bool) $configuration->get(SaferPayConfig::TEST_MODE); + $suffix = $isTestMode ? SaferPayConfig::TEST_SUFFIX : ''; + $activeUsername = (string) $configuration->get(SaferPayConfig::USERNAME . $suffix); + $activePassword = (string) $configuration->get(SaferPayConfig::PASSWORD . $suffix); + $activeCustomerId = (string) $configuration->get(SaferPayConfig::CUSTOMER_ID . $suffix); + + if (!empty($activeUsername) && !empty($activePassword) && !empty($activeCustomerId)) { + try { + /** @var SaferPayGetLicense $getLicense */ + $getLicense = $this->module->getService(SaferPayGetLicense::class); + $licenseInfo = $getLicense->fetchLicenseWithCredentials( + $activeUsername, + $activePassword, + $activeCustomerId, + $isTestMode + ); + $configuration->set( + SaferPayConfig::BUSINESS_LICENSE . $suffix, + $licenseInfo['hasBusinessLicense'] ? 1 : 0 + ); + } catch (\Exception $e) { + // Silently fall back to stored value + } + } + $data = [ // Environment 'testMode' => (bool) $configuration->get(SaferPayConfig::TEST_MODE), diff --git a/src/Api/Request/GetLicenseService.php b/src/Api/Request/GetLicenseService.php new file mode 100644 index 00000000..0299875f --- /dev/null +++ b/src/Api/Request/GetLicenseService.php @@ -0,0 +1,76 @@ + + *@copyright SIX Payment Services + *@license SIX Payment Services + */ + +namespace Invertus\SaferPay\Api\Request; + +use Invertus\SaferPay\Api\ApiRequest; +use Invertus\SaferPay\DTO\Request\GetLicense\GetLicenseRequest; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class GetLicenseService +{ + /** @var ApiRequest */ + private $apiRequest; + + public function __construct(ApiRequest $apiRequest) + { + $this->apiRequest = $apiRequest; + } + + /** + * @param GetLicenseRequest $request + * @param string $username + * @param string $password + * @param string $baseUrl + * @return mixed + */ + public function getLicense(GetLicenseRequest $request, $username, $password, $baseUrl) + { + return $this->apiRequest->getWithCredentials( + $request->generateRequestUrl(), + $username, + $password, + $baseUrl + ); + } + + /** + * @param GetLicenseRequest $request + * @param string $username + * @param string $password + * @param string $baseUrl + * @return mixed + */ + public function getLicenseFallback(GetLicenseRequest $request, $username, $password, $baseUrl) + { + return $this->apiRequest->getWithCredentials( + $request->generateFallbackRequestUrl(), + $username, + $password, + $baseUrl + ); + } +} diff --git a/src/DTO/Request/GetLicense/GetLicenseRequest.php b/src/DTO/Request/GetLicense/GetLicenseRequest.php new file mode 100644 index 00000000..0cf13981 --- /dev/null +++ b/src/DTO/Request/GetLicense/GetLicenseRequest.php @@ -0,0 +1,62 @@ + + *@copyright SIX Payment Services + *@license SIX Payment Services + */ + +namespace Invertus\SaferPay\DTO\Request\GetLicense; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class GetLicenseRequest +{ + /** @var string */ + private $customerId; + + /** + * @param string $customerId + */ + public function __construct($customerId) + { + if (!preg_match('/^[a-zA-Z0-9\-_]+$/', $customerId)) { + throw new \InvalidArgumentException('Invalid customer ID format'); + } + + $this->customerId = $customerId; + } + + /** + * @return string + */ + public function generateRequestUrl() + { + return sprintf('rest/customers/%s/license', $this->customerId); + } + + /** + * @return string + */ + public function generateFallbackRequestUrl() + { + return sprintf('rest/customers/%s/license-configuration', $this->customerId); + } +} diff --git a/src/Service/SaferPayGetLicense.php b/src/Service/SaferPayGetLicense.php new file mode 100644 index 00000000..a88e410a --- /dev/null +++ b/src/Service/SaferPayGetLicense.php @@ -0,0 +1,106 @@ + + *@copyright SIX Payment Services + *@license SIX Payment Services + */ + +namespace Invertus\SaferPay\Service; + +use Exception; +use Invertus\SaferPay\Api\Request\GetLicenseService; +use Invertus\SaferPay\Config\SaferPayConfig; +use Invertus\SaferPay\DTO\Request\GetLicense\GetLicenseRequest; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class SaferPayGetLicense +{ + const FEATURE_HOSTED_ENTRY_FORM = 'HOSTED_ENTRY_FORM'; + + /** @var GetLicenseService */ + private $getLicenseService; + + public function __construct(GetLicenseService $getLicenseService) + { + $this->getLicenseService = $getLicenseService; + } + + /** + * @param string $username + * @param string $password + * @param string $customerId + * @param bool $isTestMode + * + * @return array{hasBusinessLicense: bool, packageName: string, features: array} + * + * @throws Exception + */ + public function fetchLicenseWithCredentials($username, $password, $customerId, $isTestMode) + { + $baseUrl = $isTestMode ? SaferPayConfig::TEST_API : SaferPayConfig::API; + $request = new GetLicenseRequest($customerId); + + $response = $this->fetchWithFallback($request, $username, $password, $baseUrl); + + $packageName = ''; + if (isset($response->Package->DisplayName)) { + $packageName = $response->Package->DisplayName; + } + + $features = []; + $featureList = isset($response->Features) ? $response->Features : []; + if (is_array($featureList)) { + foreach ($featureList as $feature) { + if (isset($feature->Id)) { + $features[] = $feature->Id; + } + } + } + + $hasBusinessLicense = in_array(self::FEATURE_HOSTED_ENTRY_FORM, $features, true); + + return [ + 'hasBusinessLicense' => $hasBusinessLicense, + 'packageName' => $packageName, + 'features' => $features, + ]; + } + + /** + * @param GetLicenseRequest $request + * @param string $username + * @param string $password + * @param string $baseUrl + * + * @return mixed + * + * @throws Exception + */ + private function fetchWithFallback(GetLicenseRequest $request, $username, $password, $baseUrl) + { + try { + return $this->getLicenseService->getLicense($request, $username, $password, $baseUrl); + } catch (Exception $e) { + return $this->getLicenseService->getLicenseFallback($request, $username, $password, $baseUrl); + } + } +} diff --git a/src/Service/SettingsTranslationService.php b/src/Service/SettingsTranslationService.php index 77d03e1b..fcb534a1 100644 --- a/src/Service/SettingsTranslationService.php +++ b/src/Service/SettingsTranslationService.php @@ -139,8 +139,6 @@ private function getApiCredentialsTranslations() 'invalidCredentials' => $this->module->l('Invalid credentials. Please check your username and password.', self::FILE_NAME), 'saferpayFieldsIncluded' => $this->module->l('Saferpay Fields is included in your license', self::FILE_NAME), 'saferpayFieldsIncludedDescription' => $this->module->l('You can use hosted payment fields for a seamless checkout experience.', self::FILE_NAME), - 'saferpayFieldsNotIncluded' => $this->module->l('Saferpay Fields is not available', self::FILE_NAME), - 'saferpayFieldsNotIncludedDescription' => $this->module->l('Save valid API credentials to detect your license, or upgrade your Saferpay plan to access this feature.', self::FILE_NAME), ]; } diff --git a/views/js/admin/settings-app/src/components/settings/api-credentials.tsx b/views/js/admin/settings-app/src/components/settings/api-credentials.tsx index 2e1aa5ed..663b72d0 100644 --- a/views/js/admin/settings-app/src/components/settings/api-credentials.tsx +++ b/views/js/admin/settings-app/src/components/settings/api-credentials.tsx @@ -240,39 +240,25 @@ export function ApiCredentials() { - {/* Saferpay Fields Configuration */} - + {/* Saferpay Fields Configuration - only shown when business license detected */} + {settings.hasBusinessLicense && {t('saferpayFields')} {t('saferpayFieldsDescription')}
- {settings.hasBusinessLicense ? ( -
- -
-

- {t('saferpayFieldsIncluded')} -

-

- {t('saferpayFieldsIncludedDescription')} -

-
-
- ) : ( -
- -
-

- {t('saferpayFieldsNotIncluded')} -

-

- {t('saferpayFieldsNotIncludedDescription')} -

-
+
+ +
+

+ {t('saferpayFieldsIncluded')} +

+

+ {t('saferpayFieldsIncludedDescription')} +

- )} +
@@ -293,12 +279,11 @@ export function ApiCredentials() { placeholder={t('enterFieldAccessToken')} value={fieldAccessToken} onChange={(e) => setField('fieldAccessToken', e.target.value)} - disabled={!settings.hasBusinessLicense} className="sp-flex-1" />
- + }
diff --git a/views/js/admin/settings-app/src/context/settings-context.tsx b/views/js/admin/settings-app/src/context/settings-context.tsx index e71802dc..6abb19d3 100644 --- a/views/js/admin/settings-app/src/context/settings-context.tsx +++ b/views/js/admin/settings-app/src/context/settings-context.tsx @@ -15,6 +15,7 @@ interface SettingsContextValue { saveGeneralSettings: () => Promise savePaymentMethods: () => Promise fetchTerminals: (env: string, username: string, password: string) => Promise + generateFieldAccessToken: () => Promise<{ success: boolean; message?: string; token?: string }> refreshPaymentMethods: () => Promise paymentMethods: PaymentMethodData[] updatePaymentMethod: (name: string, updates: Partial) => void @@ -148,6 +149,26 @@ export function SettingsProvider({ children }: { children: React.ReactNode }) { } }, []) + const generateFieldAccessToken = useCallback(async () => { + const s = settingsRef.current + const env = s.testMode ? 'test' : 'live' + const username = s.testMode ? s.testUsername : s.liveUsername + const password = s.testMode ? s.testPassword : s.livePassword + const terminalId = s.testMode ? s.testTerminalId : s.liveTerminalId + + const result = await api.generateFieldAccessToken(env, username, password, terminalId) + if (!result.success) { + throw new Error(result.message || t('failedToGenerateToken')) + } + + if (result.token) { + const fieldKey = s.testMode ? 'testFieldAccessToken' : 'liveFieldAccessToken' + setSettings((prev) => ({ ...prev, [fieldKey]: result.token })) + } + + return result + }, []) + const fetchTerminals = useCallback(async (env: string, username: string, password: string) => { const result = await api.getTerminals(env, username, password) if (!result.success) { @@ -165,6 +186,7 @@ export function SettingsProvider({ children }: { children: React.ReactNode }) { saveGeneralSettings, savePaymentMethods, fetchTerminals, + generateFieldAccessToken, refreshPaymentMethods, paymentMethods, updatePaymentMethod, @@ -178,6 +200,7 @@ export function SettingsProvider({ children }: { children: React.ReactNode }) { saveGeneralSettings, savePaymentMethods, fetchTerminals, + generateFieldAccessToken, refreshPaymentMethods, paymentMethods, updatePaymentMethod,