From 1da00baf6b6337247ecaa22c11a334912d57b991 Mon Sep 17 00:00:00 2001 From: Justas Date: Fri, 10 Jul 2026 09:27:52 +0300 Subject: [PATCH 1/3] feat(SL-350): settings copy & link fixes (SL-365/368/369/372/373) - SL-368: deep-link Saferpay Fields library docs anchor - SL-369: reword 3DS liability-shift setting labels - SL-373: clarify Email Sending toggle descriptions; fold merchant-email pointer into section description - SL-372: correct misleading Hosted field style info box - SL-365: add credentials hint with env-based Backoffice link + JSON API Basic auth docs link Compiled bundle (views/js/admin/dist) is gitignored and built in CI. --- src/Service/SettingsTranslationService.php | 18 +++++++++++------- .../components/settings/api-credentials.tsx | 15 ++++++++++++++- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/Service/SettingsTranslationService.php b/src/Service/SettingsTranslationService.php index ae2de999..462466e4 100644 --- a/src/Service/SettingsTranslationService.php +++ b/src/Service/SettingsTranslationService.php @@ -109,6 +109,10 @@ private function getApiCredentialsTranslations() 'live' => $this->module->l('Live', self::FILE_NAME), 'apiCredentials' => $this->module->l('API Credentials', self::FILE_NAME), 'enterSaferpayCredentials' => $this->module->l('Enter your Saferpay %s environment API credentials.', self::FILE_NAME), + 'credentialsHintPrefix' => $this->module->l('You can generate your API credentials inside the', self::FILE_NAME), + 'credentialsBackofficeLinkText' => $this->module->l('Saferpay Backoffice', self::FILE_NAME), + 'credentialsHintMiddle' => html_entity_decode($this->module->l('under Settings > JSON API Basic authentication.', self::FILE_NAME), ENT_QUOTES, 'UTF-8'), + 'credentialsMoreInfoLinkText' => $this->module->l('More information', self::FILE_NAME), 'jsonApiUsername' => $this->module->l('JSON API Username', self::FILE_NAME), 'enterApiUsername' => $this->module->l('Enter %s API username', self::FILE_NAME), 'jsonApiPassword' => $this->module->l('JSON API Password', self::FILE_NAME), @@ -176,11 +180,11 @@ private function getPaymentProcessingTranslations() 'chargeImmediately' => $this->module->l('Charge immediately', self::FILE_NAME), 'authorize' => $this->module->l('Authorize', self::FILE_NAME), 'reserveAndCaptureLater' => $this->module->l('Reserve and capture later', self::FILE_NAME), - 'behaviourWhen3dsFails' => $this->module->l('Behavior when 3D Secure Payer Authentication was not successful and liability shift was not granted', self::FILE_NAME), + 'behaviourWhen3dsFails' => $this->module->l('Behavior when liability shift through 3D Secure has not been granted', self::FILE_NAME), 'behaviourWhen3dsDescription' => $this->module->l('Default payment behavior for payment without 3-D Secure.', self::FILE_NAME), 'cancel' => $this->module->l('Cancel', self::FILE_NAME), 'rejectPayment' => $this->module->l('Reject the payment', self::FILE_NAME), - 'continueWithout3ds' => $this->module->l('Continue without 3DS', self::FILE_NAME), + 'continueWithout3ds' => $this->module->l('Cancel or Capture manually', self::FILE_NAME), 'captureWithout3ds' => $this->module->l('Charge immediately', self::FILE_NAME), 'restrictRefundAmount' => $this->module->l('Restrict RefundAmount to Captured Amount', self::FILE_NAME), 'restrictRefundDescription' => $this->module->l('If set to true, the refund will be rejected if the sum of authorized refunds exceeds the capture value.', self::FILE_NAME), @@ -205,13 +209,13 @@ private function getEmailTranslations() { return [ 'emailSending' => $this->module->l('Email Sending', self::FILE_NAME), - 'emailSendingDescription' => $this->module->l('Configure which emails are sent during the payment process.', self::FILE_NAME), + 'emailSendingDescription' => $this->module->l('Configure which emails are sent during the payment process. Merchant notifications sent by Saferpay use the Merchant Email(s) field on the Credentials tab.', self::FILE_NAME), 'saferpayCustomerMail' => $this->module->l('Send an email from Saferpay on payment completion', self::FILE_NAME), - 'saferpayCustomerMailDescription' => $this->module->l('With this setting enabled an email from the Saferpay system will be sent to the customer.', self::FILE_NAME), + 'saferpayCustomerMailDescription' => $this->module->l('Saferpay sends a payment confirmation email directly to the customer.', self::FILE_NAME), 'newOrderMail' => $this->module->l('Send new order mail on authorization', self::FILE_NAME), - 'newOrderMailDescription' => $this->module->l('Receive a notification when an order is authorized by Saferpay (Using the Mail alert module).', self::FILE_NAME), + 'newOrderMailDescription' => $this->module->l('Notify the shop owner when an order is authorized (requires the Mail Alert module).', self::FILE_NAME), 'orderConfMail' => $this->module->l('Send order confirmation mail on payment completion', self::FILE_NAME), - 'orderConfMailDescription' => $this->module->l('Send an email from Saferpay on payment completion.', self::FILE_NAME), + 'orderConfMailDescription' => $this->module->l('Send the shop\'s order confirmation email to the customer, only once payment is authorized by Saferpay.', self::FILE_NAME), 'emailConfInfo' => $this->module->l('When this feature is enabled, a confirmation email will be only sent once the payment is authorized by Saferpay.', self::FILE_NAME), 'emailConfMailAlert' => $this->module->l('For this feature to be functioning you need to have the Mail Alert module configured.', self::FILE_NAME), ]; @@ -230,7 +234,7 @@ private function getGeneralSettingsTranslations() 'configName' => $this->module->l('Payment Page configurations name', self::FILE_NAME), 'enterConfigName' => $this->module->l('Enter configuration name', self::FILE_NAME), 'configNameDescription' => html_entity_decode($this->module->l('Name of the Payment Page Configuration created in Saferpay Backoffice (Settings > Payment Page Configuration). Max 20 characters. Allowed: letters, numbers, dots, colons, hyphens, underscores.', self::FILE_NAME), ENT_QUOTES, 'UTF-8'), - 'hostedFieldInfo' => html_entity_decode($this->module->l('This style applies only to payment methods with "Custom form" enabled in the Payment Methods list. Methods without Custom form or paid with saved cards use the Saferpay-hosted payment page, whose appearance is controlled by "Payment Page configurations name".', self::FILE_NAME), ENT_QUOTES, 'UTF-8'), + 'hostedFieldInfo' => html_entity_decode($this->module->l('"Hosted field style" sets how the card input form looks on the Saferpay-hosted payment page. It doesn\'t affect the custom form or saved-card payments.', self::FILE_NAME), ENT_QUOTES, 'UTF-8'), 'hostedFieldStyle' => $this->module->l('Hosted field style', self::FILE_NAME), 'hostedFieldStyleDescription' => $this->module->l('Select the card input form layout for the payment page.', self::FILE_NAME), 'classicLayout' => $this->module->l('Classic Layout', 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 d806211b..3dead4bc 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 @@ -26,6 +26,8 @@ export function ApiCredentials() { const prefix = isTest ? 'test' : 'live' const envLabel = isTest ? t('test') : t('live') const environment = isTest ? 'test' : 'live' + const backofficeUrl = isTest ? 'https://test.saferpay.com/bo/login' : 'https://www.saferpay.com/bo/login' + const jsonApiBasicAuthDocsUrl = 'https://docs.saferpay.com/home/interfaces/backoffice/settings/json-api-basic-client-certificate-authentication#basic-authentication' const username = isTest ? settings.testUsername : settings.liveUsername const password = isTest ? settings.testPassword : settings.livePassword @@ -150,6 +152,17 @@ export function ApiCredentials() {
+ {/* Credentials generation hint */} +
+ +

+ {t('credentialsHintPrefix')}{' '} + {t('credentialsBackofficeLinkText')}{' '} + {t('credentialsHintMiddle')}{' '} + {t('credentialsMoreInfoLinkText')} +

+
+ {/* Username & Password */}
@@ -343,7 +356,7 @@ export function ApiCredentials() { value={fieldJsUrl} onChange={(e) => setField('fieldJsUrl', e.target.value)} /> - + {t('findLibraryUrlHere')}
From 074bd4454de881f1d200f641b543ba9ccc6133d9 Mon Sep 17 00:00:00 2001 From: Justas Date: Fri, 10 Jul 2026 11:33:27 +0300 Subject: [PATCH 2/3] refactor(SL-365/373): single translatable credentials hint; correct tab name Address review feedback: - Combine the split credentials-hint keys into one translatable string with [backoffice_link]/[more_info_link] placeholders, parsed in the component (avoids the i18n sentence-splitting anti-pattern). - Refer to the 'API Credentials' tab by its actual name in the Email Sending description. --- src/Service/SettingsTranslationService.php | 5 ++--- .../src/components/settings/api-credentials.tsx | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/Service/SettingsTranslationService.php b/src/Service/SettingsTranslationService.php index 462466e4..29772358 100644 --- a/src/Service/SettingsTranslationService.php +++ b/src/Service/SettingsTranslationService.php @@ -109,9 +109,8 @@ private function getApiCredentialsTranslations() 'live' => $this->module->l('Live', self::FILE_NAME), 'apiCredentials' => $this->module->l('API Credentials', self::FILE_NAME), 'enterSaferpayCredentials' => $this->module->l('Enter your Saferpay %s environment API credentials.', self::FILE_NAME), - 'credentialsHintPrefix' => $this->module->l('You can generate your API credentials inside the', self::FILE_NAME), + 'credentialsHint' => html_entity_decode($this->module->l('You can generate your API credentials inside the [backoffice_link] under Settings > JSON API Basic authentication. [more_info_link]', self::FILE_NAME), ENT_QUOTES, 'UTF-8'), 'credentialsBackofficeLinkText' => $this->module->l('Saferpay Backoffice', self::FILE_NAME), - 'credentialsHintMiddle' => html_entity_decode($this->module->l('under Settings > JSON API Basic authentication.', self::FILE_NAME), ENT_QUOTES, 'UTF-8'), 'credentialsMoreInfoLinkText' => $this->module->l('More information', self::FILE_NAME), 'jsonApiUsername' => $this->module->l('JSON API Username', self::FILE_NAME), 'enterApiUsername' => $this->module->l('Enter %s API username', self::FILE_NAME), @@ -209,7 +208,7 @@ private function getEmailTranslations() { return [ 'emailSending' => $this->module->l('Email Sending', self::FILE_NAME), - 'emailSendingDescription' => $this->module->l('Configure which emails are sent during the payment process. Merchant notifications sent by Saferpay use the Merchant Email(s) field on the Credentials tab.', self::FILE_NAME), + 'emailSendingDescription' => $this->module->l('Configure which emails are sent during the payment process. Merchant notifications sent by Saferpay use the Merchant Email(s) field on the API Credentials tab.', self::FILE_NAME), 'saferpayCustomerMail' => $this->module->l('Send an email from Saferpay on payment completion', self::FILE_NAME), 'saferpayCustomerMailDescription' => $this->module->l('Saferpay sends a payment confirmation email directly to the customer.', self::FILE_NAME), 'newOrderMail' => $this->module->l('Send new order mail on authorization', 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 3dead4bc..70499b94 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 @@ -156,10 +156,19 @@ export function ApiCredentials() {

- {t('credentialsHintPrefix')}{' '} - {t('credentialsBackofficeLinkText')}{' '} - {t('credentialsHintMiddle')}{' '} - {t('credentialsMoreInfoLinkText')} + {t('credentialsHint').split(/(\[backoffice_link\]|\[more_info_link\])/).map((part, index) => { + if (part === '[backoffice_link]') { + return ( + {t('credentialsBackofficeLinkText')} + ) + } + if (part === '[more_info_link]') { + return ( + {t('credentialsMoreInfoLinkText')} + ) + } + return part + })}

From b9a00d012abbdff4cb7f19f73a35273f22a711dc Mon Sep 17 00:00:00 2001 From: Justas Date: Fri, 10 Jul 2026 16:26:19 +0300 Subject: [PATCH 3/3] fix preview pass --- .../components/settings/api-credentials.tsx | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) 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 70499b94..70896142 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 @@ -12,6 +12,10 @@ import type { TerminalOption } from '@/types' type CredentialStatus = 'idle' | 'checking' | 'valid' | 'invalid' +// Mirrors AdminSaferPayOfficialSettingsController::PASSWORD_PLACEHOLDER. The real +// password is never sent to the browser; a stored credential arrives as this mask. +const PASSWORD_PLACEHOLDER = '********' + export function ApiCredentials() { const { settings, updateSettings, saveCredentials, fetchTerminals, generateFieldAccessToken, savingSections } = useSettings() const saving = savingSections.has('credentials') @@ -31,6 +35,9 @@ export function ApiCredentials() { const username = isTest ? settings.testUsername : settings.liveUsername const password = isTest ? settings.testPassword : settings.livePassword + // While the field still holds the untouched stored-credential mask there is nothing + // to reveal, so the show/hide toggle is hidden until the user types a new password. + const isStoredPasswordMasked = password === PASSWORD_PLACEHOLDER const terminalId = isTest ? settings.testTerminalId : settings.liveTerminalId const merchantEmails = isTest ? settings.testMerchantEmails : settings.liveMerchantEmails const fieldAccessToken = isTest ? settings.testFieldAccessToken : settings.liveFieldAccessToken @@ -195,22 +202,24 @@ export function ApiCredentials() {
setField('password', e.target.value)} - className="sp-pr-10" + className={isStoredPasswordMasked ? undefined : 'sp-pr-10'} required aria-required="true" /> - + {!isStoredPasswordMasked && ( + + )}