diff --git a/app/frontend/src/javascript/api/setting.ts b/app/frontend/src/javascript/api/setting.ts index 6e6ce3128..187e75564 100644 --- a/app/frontend/src/javascript/api/setting.ts +++ b/app/frontend/src/javascript/api/setting.ts @@ -22,16 +22,26 @@ export default class SettingAPI { return SettingAPI.toBulkMap(res?.data?.settings); } + async isPresent (name: SettingName): Promise { + const res: AxiosResponse = await apiClient.get(`/api/settings/is_present/${name}`); + return res?.data?.isPresent; + } + static get (name: SettingName): IWrapPromise { const api = new SettingAPI(); return wrapPromise(api.get(name)); } - static query(names: Array): IWrapPromise> { + static query (names: Array): IWrapPromise> { const api = new SettingAPI(); return wrapPromise(api.query(names)); } + static isPresent (name: SettingName): IWrapPromise { + const api = new SettingAPI(); + return wrapPromise(api.isPresent(name)); + } + private static toSettingsMap(data: Object): Map { const dataArray: Array> = Object.entries(data); const map = new Map(); diff --git a/app/frontend/src/javascript/components/fab-input.tsx b/app/frontend/src/javascript/components/fab-input.tsx index bf44f1bb6..635f2ea43 100644 --- a/app/frontend/src/javascript/components/fab-input.tsx +++ b/app/frontend/src/javascript/components/fab-input.tsx @@ -16,16 +16,19 @@ interface FabInputProps { disabled?: boolean, required?: boolean, debounce?: number, + readOnly?: boolean, type?: 'text' | 'date' | 'password' | 'url' | 'time' | 'tel' | 'search' | 'number' | 'month' | 'email' | 'datetime-local' | 'week', } -export const FabInput: React.FC = ({ id, onChange, defaultValue, icon, className, disabled, type, required, debounce, addOn, addOnClassName }) => { +export const FabInput: React.FC = ({ id, onChange, defaultValue, icon, className, disabled, type, required, debounce, addOn, addOnClassName, readOnly }) => { const [inputValue, setInputValue] = useState(defaultValue); useEffect(() => { if (!inputValue) { setInputValue(defaultValue); - onChange(defaultValue); + if (typeof onChange === 'function') { + onChange(defaultValue); + } } }, [defaultValue]); @@ -46,7 +49,7 @@ export const FabInput: React.FC = ({ id, onChange, defaultValue, /** * Debounced (ie. temporised) version of the 'on change' callback. */ - const debouncedOnChange = useCallback(_debounce(onChange, debounce), [onChange, debounce]); + const debouncedOnChange = debounce ? useCallback(_debounce(onChange, debounce), [onChange, debounce]) : null; /** * Handle the change of content in the input field, and trigger the parent callback, if any @@ -66,11 +69,10 @@ export const FabInput: React.FC = ({ id, onChange, defaultValue, return (
{hasIcon() && {icon}} - + {hasAddOn() && {addOn}}
); } FabInput.defaultProps = { type: 'text', debounce: 0 }; - diff --git a/app/frontend/src/javascript/components/payzen-keys-form.tsx b/app/frontend/src/javascript/components/payzen-keys-form.tsx index feab6d545..e8df6f94f 100644 --- a/app/frontend/src/javascript/components/payzen-keys-form.tsx +++ b/app/frontend/src/javascript/components/payzen-keys-form.tsx @@ -125,7 +125,7 @@ const PayZenKeysFormComponent: React.FC = ({ onValidKeys })
{t('app.admin.invoices.payment.client_keys')}
- + } defaultValue={settings.get(SettingName.PayZenPublicKey)} @@ -142,7 +142,7 @@ const PayZenKeysFormComponent: React.FC = ({ onValidKeys }) {hasApiAddOn() && {restApiAddOn}}
- + } @@ -152,7 +152,7 @@ const PayZenKeysFormComponent: React.FC = ({ onValidKeys }) required />
- + } defaultValue={settings.get(SettingName.PayZenPassword)} @@ -161,7 +161,7 @@ const PayZenKeysFormComponent: React.FC = ({ onValidKeys }) required />
- + } @@ -171,7 +171,7 @@ const PayZenKeysFormComponent: React.FC = ({ onValidKeys }) required />
- + } defaultValue={settings.get(SettingName.PayZenHmacKey)} diff --git a/app/frontend/src/javascript/components/payzen-settings.tsx b/app/frontend/src/javascript/components/payzen-settings.tsx new file mode 100644 index 000000000..c87417118 --- /dev/null +++ b/app/frontend/src/javascript/components/payzen-settings.tsx @@ -0,0 +1,128 @@ +/** + * This component displays a summary of the PayZen account keys, with a button triggering the modal to edit them + */ + +import React, { useEffect, useState } from 'react'; +import { Loader } from './loader'; +import { react2angular } from 'react2angular'; +import { IApplication } from '../models/application'; +import { useTranslation } from 'react-i18next'; +import SettingAPI from '../api/setting'; +import { SettingName } from '../models/setting'; +import { useImmer } from 'use-immer'; +import { FabInput } from './fab-input'; +import { FabButton } from './fab-button'; +import { FabModal, ModalSize } from './fab-modal'; +import { PayZenKeysForm } from './payzen-keys-form'; + +declare var Application: IApplication; + +interface PayzenSettingsProps { + +} + +const PAYZEN_HIDDEN = 'testpassword_HiDdEnHIddEnHIdDEnHiDdEnHIddEnHIdDEn'; +const payZenPublicSettings: Array = [SettingName.PayZenPublicKey, SettingName.PayZenEndpoint, SettingName.PayZenUsername]; +const payZenPrivateSettings: Array = [SettingName.PayZenPassword, SettingName.PayZenHmacKey]; +const payZenSettings: Array = payZenPublicSettings.concat(payZenPrivateSettings); +const icons:Map = new Map([ + [SettingName.PayZenHmacKey, 'subscript'], + [SettingName.PayZenPassword, 'key'], + [SettingName.PayZenUsername, 'user'], + [SettingName.PayZenEndpoint, 'link'], + [SettingName.PayZenPublicKey, 'info'] +]) + +const payZenKeys = SettingAPI.query(payZenPublicSettings); +const isPresent = { + [SettingName.PayZenPassword]: SettingAPI.isPresent(SettingName.PayZenPassword), + [SettingName.PayZenHmacKey]: SettingAPI.isPresent(SettingName.PayZenHmacKey) +}; + +export const PayzenSettings: React.FC = ({}) => { + const { t } = useTranslation('admin'); + + const [settings, updateSettings] = useImmer>(new Map(payZenSettings.map(name => [name, '']))); + const [openEditModal, setOpenEditModal] = useState(false); + const [preventConfirm, setPreventConfirm] = useState(true); + const [config, setConfig] = useState>(new Map()); + const [errors, setErrors] = useState(''); + + useEffect(() => { + const map = payZenKeys.read(); + for (const setting of payZenPrivateSettings) { + map.set(setting, isPresent[setting].read() ? PAYZEN_HIDDEN : ''); + } + updateSettings(map); + }, []); + + /** + * Open/closes the modal dialog to edit the payzen keys + */ + const toggleEditKeysModal = () => { + setOpenEditModal(!openEditModal); + } + + const handleUpdateKeys = () => { + const api = new SettingAPI(); + api.bulkUpdate(settings).then(result => { + if (Array.from(result.values()).filter(item => !item.status).length > 0) { + setErrors(JSON.stringify(result)); + } else { + // TODO updateSettings(result); + toggleEditKeysModal(); + } + }, reason => { + setErrors(reason); + }); + } + + const handleValidPayZenKeys = (payZenKeys: Map): void => { + setConfig(payZenKeys); + setPreventConfirm(false); + } + + return ( +
+

{t('app.admin.invoices.payment.payzen.payzen_keys')}

+
+ {payZenSettings.map(setting => { + return ( +
+ + -1 ? 'password' : 'text'} + icon={} + readOnly + disabled /> +
+ ); + })} +
+ {t('app.admin.invoices.payment.edit_keys')} + + {errors && {errors}} + + +
+ ); +} + + +const PayzenSettingsWrapper: React.FC = ({}) => { + return ( + + + + ); +} + +Application.Components.component('payzenSettings', react2angular(PayzenSettingsWrapper)); diff --git a/app/frontend/src/javascript/components/select-gateway-modal.tsx b/app/frontend/src/javascript/components/select-gateway-modal.tsx index 8b3177218..2d6b1bdcb 100644 --- a/app/frontend/src/javascript/components/select-gateway-modal.tsx +++ b/app/frontend/src/javascript/components/select-gateway-modal.tsx @@ -110,7 +110,6 @@ const SelectGatewayModal: React.FC = ({ isOpen, to isOpen={isOpen} toggleModal={toggleModal} width={ModalSize.medium} - closeButton={false} className="gateway-modal" confirmButton={t('app.admin.invoices.payment.gateway_modal.confirm_button')} onConfirm={onGatewayConfirmed} diff --git a/app/frontend/src/javascript/controllers/admin/invoices.js b/app/frontend/src/javascript/controllers/admin/invoices.js index cef9a18bc..0e0c520e3 100644 --- a/app/frontend/src/javascript/controllers/admin/invoices.js +++ b/app/frontend/src/javascript/controllers/admin/invoices.js @@ -689,17 +689,6 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I $scope.onlinePaymentStatus = res.status; }); } - if ($scope.allSettings.payment_gateway === 'stripe') { - $scope.allSettings.payzen_username = updatedSettings.get('payzen_username').value; - $scope.allSettings.payzen_endpoint = updatedSettings.get('payzen_endpoint').value; - $scope.allSettings.payzen_public_key = updatedSettings.get('payzen_public_key').value; - Setting.isPresent({ name: 'payzen_password' }, function (res) { - $scope.allSettings.payzen_password = (res.isPresent ? PAYZEN_PASSWD_HIDDEN : ''); - }); - Setting.isPresent({ name: 'payzen_hmac' }, function (res) { - $scope.allSettings.payzen_hmac = (res.isPresent ? PAYZEN_PASSWD_HIDDEN : ''); - }); - } }; /** diff --git a/app/frontend/src/javascript/router.js b/app/frontend/src/javascript/router.js index a0e85c3a1..4433197ba 100644 --- a/app/frontend/src/javascript/router.js +++ b/app/frontend/src/javascript/router.js @@ -859,7 +859,7 @@ angular.module('application.router', ['ui.router']) "'accounting_other_client_code', 'accounting_other_client_label', 'accounting_wallet_code', 'accounting_wallet_label', " + "'accounting_VAT_code', 'accounting_VAT_label', 'accounting_subscription_code', 'accounting_subscription_label', " + "'accounting_Machine_code', 'accounting_Machine_label', 'accounting_Training_code', 'accounting_Training_label', " + - "'accounting_Event_code', 'accounting_Event_label', 'accounting_Space_code', 'accounting_Space_label', " + + "'accounting_Event_code', 'accounting_Event_label', 'accounting_Space_code', 'accounting_Space_label', 'payment_gateway', " + "'feature_tour_display', 'online_payment_module', 'stripe_public_key', 'stripe_currency', 'invoice_prefix']" }).$promise; }], diff --git a/app/frontend/src/stylesheets/application.scss b/app/frontend/src/stylesheets/application.scss index d3893cb23..5c2aebbe4 100644 --- a/app/frontend/src/stylesheets/application.scss +++ b/app/frontend/src/stylesheets/application.scss @@ -37,5 +37,6 @@ @import "modules/select-gateway-modal"; @import "modules/stripe-keys-form"; @import "modules/payzen-keys-form"; +@import "modules/payzen-settings"; @import "app.responsive"; diff --git a/app/frontend/src/stylesheets/modules/fab-input.scss b/app/frontend/src/stylesheets/modules/fab-input.scss index 5efa79a6e..c05be5140 100644 --- a/app/frontend/src/stylesheets/modules/fab-input.scss +++ b/app/frontend/src/stylesheets/modules/fab-input.scss @@ -38,6 +38,11 @@ border-radius: 0; box-shadow: inset 0 1px 1px rgba(0, 0, 0, .08); transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + + &[disabled], &[readonly] { + background-color: #eee; + opacity: 1; + } } &--addon { diff --git a/app/frontend/src/stylesheets/modules/payzen-settings.scss b/app/frontend/src/stylesheets/modules/payzen-settings.scss new file mode 100644 index 000000000..1fc6ec47e --- /dev/null +++ b/app/frontend/src/stylesheets/modules/payzen-settings.scss @@ -0,0 +1,16 @@ +.payzen-settings { + margin: 15px; + .payzen-keys { + display: flex; + flex-direction: row; + justify-content: space-around; + flex-wrap: wrap; + + .key-wrapper { + padding: 5px; + } + } + .edit-keys-btn { + margin-top: 20px; + } +} diff --git a/app/frontend/templates/admin/invoices/payment.html b/app/frontend/templates/admin/invoices/payment.html index 248c3cccc..174bb9c78 100644 --- a/app/frontend/templates/admin/invoices/payment.html +++ b/app/frontend/templates/admin/invoices/payment.html @@ -19,7 +19,7 @@ on-success="onGatewayModalSuccess" on-error="onGatewayModalError" />
-
+

{{ 'app.admin.invoices.payment.stripe_keys' }}

@@ -47,7 +47,7 @@
-
+

{{ 'app.admin.invoices.payment.currency' }}

@@ -64,5 +64,8 @@
+
+ +
diff --git a/app/views/api/settings/test_present.json.jbuilder b/app/views/api/settings/test_present.json.jbuilder index 661b3b928..252c13588 100644 --- a/app/views/api/settings/test_present.json.jbuilder +++ b/app/views/api/settings/test_present.json.jbuilder @@ -1 +1,3 @@ +# frozen_string_literal: true + json.isPresent @setting.present? diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index 045cc7c99..ab06d5270 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -641,16 +641,20 @@ en: payzen_keys_info_html: "

To be able to collect online payments, you must configure the PayZen identifiers and keys.

Retrieve them from your merchant back office.

" client_keys: "Client key" api_keys: "API keys" - username: "User" - password: "Password" - endpoint: "REST API server name" - hmac: "HMAC-SHA-256 key" edit_keys: "Edit keys" currency: "Currency" currency_info_html: "Please specify below the currency used for online payment. You should provide a three-letter ISO code, from the list of Stripe supported currencies." currency_alert_html: "Warning: the currency cannot be changed after the first online payment was made. Please define this setting carefully before opening Fab-manager to your members." stripe_currency: "Stripe currency" gateway_configuration_error: "An error occurred while configuring the payment gateway." + payzen: + payzen_keys: "PayZen keys" + payzen_username: "Username" + payzen_password: "Password" + payzen_endpoint: "REST API server name" + payzen_hmac: "HMAC-SHA-256 key" + payzen_public_key: "Client public key" + update_button: "Update" # select a payment gateway gateway_modal: select_gateway_title: "Select a payment gateway" diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index 33077ec9e..63493f7f1 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -641,16 +641,20 @@ fr: payzen_keys_info_html: "

Pour pouvoir encaisser des paiements en ligne, vous devez configurer les identifiants et les clefs PayZen.

Retrouvez les dans votre back office marchant.

" client_keys: "Clef client" api_keys: "Clefs d'API" - username: "Utilisateur" - password: "Mot de passe" - endpoint: "Nom du serveur de l'API REST" - hmac: "Clef HMAC-SHA-256" edit_keys: "Modifier les clefs" currency: "Devise" currency_info_html: "Veuillez indiquer la devise à utiliser lors des paiements en ligne. Vous devez fournir un code ISO à trois lettres, issu de la liste des devises supportées par Stripe." currency_alert_html: "Attention : la devise ne peut pas être modifiée après que le premier paiement en ligne ait été effectué. Veuillez définir attentivement ce paramètre avant d'ouvrir Fab-manager à vos membres." stripe_currency: "Devise Stripe" gateway_configuration_error: "Une erreur est survenue lors de la configuration de la passerelle de paiement." + payzen: + payzen_keys: "Clefs PayZen" + payzen_username: "Utilisateur" + payzen_password: "Mot de passe" + payzen_endpoint: "Nom du serveur de l'API REST" + payzen_hmac: "Clef HMAC-SHA-256" + payzen_public_key: "Clef publique client" + update_button: "Mettre à jour" # select a payment gateway gateway_modal: select_gateway_title: "Sélectionnez une passerelle de paiement"