diff --git a/CHANGELOG.md b/CHANGELOG.md index df4eea9c4..012cfe4cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Next release - [TODO DEPLOY] `rails fablab:stripe:set_gateway` +- [TODO DEPLOY] `rails fablab:maintenance:rebuild_stylesheet` ## v4.7.7 (pending) - Enforced validation on required input fields diff --git a/app/frontend/src/javascript/components/fab-input.tsx b/app/frontend/src/javascript/components/fab-input.tsx index 5448ef8ec..56174ff74 100644 --- a/app/frontend/src/javascript/components/fab-input.tsx +++ b/app/frontend/src/javascript/components/fab-input.tsx @@ -27,7 +27,9 @@ export const FabInput: React.FC = ({ id, onChange, value, icon, c useEffect(() => { setInputValue(value); - onChange(value); + if (value) { + onChange(value); + } }, [value]); /** diff --git a/app/frontend/src/javascript/components/payzen-keys-form.tsx b/app/frontend/src/javascript/components/payzen-keys-form.tsx new file mode 100644 index 000000000..53b908de5 --- /dev/null +++ b/app/frontend/src/javascript/components/payzen-keys-form.tsx @@ -0,0 +1,159 @@ +/** + * Form to set the PayZen's username, password and public key + */ + +import React, { ReactNode, useEffect, useState } from 'react'; +import { Loader } from './loader'; +import { useTranslation } from 'react-i18next'; +import SettingAPI from '../api/setting'; +import { SettingName } from '../models/setting'; +import { FabInput } from './fab-input'; + + +interface PayZenKeysFormProps { + onValidKeys: (payZenSettings: Map) => void +} + +const payZenSettings = [SettingName.PayZenUsername, SettingName.PayZenPassword, SettingName.PayZenEndpoint, SettingName.PayZenHmacKey, SettingName.PayZenPublicKey]; +const payZenKeys = SettingAPI.query(payZenSettings); + +const PayZenKeysFormComponent: React.FC = ({ onValidKeys }) => { + const { t } = useTranslation('admin'); + + const defaultSettings: [SettingName, string][] = payZenSettings.map(name => [name, '']); + const [settings, setSettings] = useState>(new Map(defaultSettings)); + const [restApiAddOn, setRestApiAddOn] = useState(null); + const [restApiAddOnClassName, setRestApiAddOnClassName] = useState(''); + const [publicKeyAddOn, setPublicKeyAddOn] = useState(null); + const [publicKeyAddOnClassName, setPublicKeyAddOnClassName] = useState(''); + + useEffect(() => { + setSettings(payZenKeys.read()); + }, []); + + useEffect(() => { + const validClassName = 'key-valid'; + if (publicKeyAddOnClassName === validClassName && restApiAddOnClassName === validClassName) { + onValidKeys(settings); + } + }, [publicKeyAddOnClassName, restApiAddOnClassName]); + + + /** + * Check if the inputted public key is valid and assign it to the settings if the key is valid + */ + const testPublicKey = (key: string) => { + if (!key.match(/^[0-9]+:/)) { + setPublicKeyAddOn(); + setPublicKeyAddOnClassName('key-invalid'); + return; + } + setSettings(new Map(settings).set(SettingName.PayZenPublicKey, key)); + setPublicKeyAddOn(); + setPublicKeyAddOnClassName('key-valid'); + } + + /** + * Send a test call to the payZen REST API to check if the inputted settings key are valid + */ + const testRestApi = (setting: SettingName.PayZenUsername | SettingName.PayZenPassword | SettingName.PayZenEndpoint | SettingName.PayZenHmacKey) => { + return (key: string) => { + // if (!key.match(/^sk_/)) { + setRestApiAddOn(); + setRestApiAddOnClassName('key-invalid'); + return; + // } + // StripeAPI.listAllCharges(key).then(() => { + // setSecretKey(key); + // setSecretKeyAddOn(); + // setSecretKeyAddOnClassName('key-valid'); + // }, reason => { + // if (reason.response.status === 401) { + // setSecretKeyAddOn(); + // setSecretKeyAddOnClassName('key-invalid'); + // } + // }); + }; + } + + /** + * Check if an add-on icon must be shown for the API settings + */ + const hasApiAddOn = () => { + return restApiAddOn !== null; + } + + return ( +
+
+
+
+ {t('app.admin.invoices.payment.client_keys')} +
+ + } + value={settings.get(SettingName.PayZenPublicKey)} + onChange={testPublicKey} + addOn={publicKeyAddOn} + addOnClassName={publicKeyAddOnClassName} + debounce={200} + required /> +
+
+
+ + {t('app.admin.invoices.payment.api_keys')} + {hasApiAddOn() && {restApiAddOn}} + +
+ + } + value={settings.get(SettingName.PayZenUsername)} + onChange={testRestApi(SettingName.PayZenUsername)} + debounce={200} + required /> +
+
+ + } + value={settings.get(SettingName.PayZenPassword)} + onChange={testRestApi(SettingName.PayZenPassword)} + debounce={200} + required /> +
+
+ + } + value={settings.get(SettingName.PayZenEndpoint)} + onChange={testRestApi(SettingName.PayZenEndpoint)} + debounce={200} + required /> +
+
+ + } + value={settings.get(SettingName.PayZenHmacKey)} + onChange={testRestApi(SettingName.PayZenHmacKey)} + debounce={200} + required /> +
+
+
+
+ ); +} + +export const PayZenKeysForm: React.FC = ({ onValidKeys }) => { + return ( + + + + ); +} diff --git a/app/frontend/src/javascript/components/select-gateway-modal.tsx b/app/frontend/src/javascript/components/select-gateway-modal.tsx index 7a3abb843..891d7f095 100644 --- a/app/frontend/src/javascript/components/select-gateway-modal.tsx +++ b/app/frontend/src/javascript/components/select-gateway-modal.tsx @@ -14,6 +14,7 @@ import { Gateway } from '../models/gateway'; import { StripeKeysForm } from './stripe-keys-form'; import { SettingBulkResult, SettingName } from '../models/setting'; import SettingAPI from '../api/setting'; +import { PayZenKeysForm } from './payzen-keys-form'; declare var Application: IApplication; @@ -116,6 +117,7 @@ const SelectGatewayModal: React.FC = ({ isOpen, to {selectedGateway === Gateway.Stripe && } + {selectedGateway === Gateway.PayZen && } ); }; diff --git a/app/frontend/src/javascript/components/stripe-keys-form.tsx b/app/frontend/src/javascript/components/stripe-keys-form.tsx index e4f323c43..32f8fb4b0 100644 --- a/app/frontend/src/javascript/components/stripe-keys-form.tsx +++ b/app/frontend/src/javascript/components/stripe-keys-form.tsx @@ -2,7 +2,7 @@ * Form to set the stripe's public and private keys */ -import React, { BaseSyntheticEvent, ReactNode, useEffect, useState } from 'react'; +import React, { ReactNode, useEffect, useState } from 'react'; import { Loader } from './loader'; import { useTranslation } from 'react-i18next'; import SettingAPI from '../api/setting'; diff --git a/app/frontend/src/javascript/models/setting.ts b/app/frontend/src/javascript/models/setting.ts index d587a5642..372e5c86b 100644 --- a/app/frontend/src/javascript/models/setting.ts +++ b/app/frontend/src/javascript/models/setting.ts @@ -102,7 +102,12 @@ export enum SettingName { PaymentSchedulePrefix = 'payment_schedule_prefix', TrainingsModule = 'trainings_module', AddressRequired = 'address_required', - PaymentGateway = 'payment_gateway' + PaymentGateway = 'payment_gateway', + PayZenUsername = 'payzen_username', + PayZenPassword = 'payzen_password', + PayZenEndpoint = 'payzen_endpoint', + PayZenPublicKey = 'payzen_public_key', + PayZenHmacKey = 'payzen_hmac' } export interface Setting { diff --git a/app/frontend/src/stylesheets/application.scss b/app/frontend/src/stylesheets/application.scss index dd0c3d887..d3893cb23 100644 --- a/app/frontend/src/stylesheets/application.scss +++ b/app/frontend/src/stylesheets/application.scss @@ -36,5 +36,6 @@ @import "modules/plan-card"; @import "modules/select-gateway-modal"; @import "modules/stripe-keys-form"; +@import "modules/payzen-keys-form"; @import "app.responsive"; diff --git a/app/frontend/src/stylesheets/modules/payzen-keys-form.scss b/app/frontend/src/stylesheets/modules/payzen-keys-form.scss new file mode 100644 index 000000000..68ee203f9 --- /dev/null +++ b/app/frontend/src/stylesheets/modules/payzen-keys-form.scss @@ -0,0 +1,55 @@ +.payzen-keys-form { + & { + margin-top: 1em; + } + + .payzen-keys-info { + border: 1px solid #bce8f1; + border-radius: 4px; + color: #31708f; + background-color: #d9edf7; + padding: 15px; + } + + fieldset { + border: 1px solid #c4c4c4; + border-radius: 4px; + margin-top: 1em; + padding: 7px; + + & > legend { + padding: 3px 6px; + width: fit-content; + font-size: 1em; + border-radius: 4px; + margin-left: 1em; + margin-bottom: 0; + position: relative; + + &.with-addon { + border-radius: 4px 0 0 4px;; + } + } + .fieldset-legend--addon { + display: block; + position: absolute; + top: 0; + right: -35px; + font-size: 1em; + padding: 3px 12px; + font-weight: 400; + text-align: center; + border-radius: 0 4px 4px 0; + vertical-align: middle; + } + } + + .key-valid { + background-color: #7bca38; + } + + .key-invalid { + background-color: #d92227; + color: white; + } +} diff --git a/app/models/setting.rb b/app/models/setting.rb index 7c04ec9fe..e649e0f72 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -110,7 +110,12 @@ class Setting < ApplicationRecord payment_schedule_prefix trainings_module address_required - payment_gateway] } + payment_gateway + payzen_username + payzen_password + payzen_endpoint + payzen_public_key + payzen_hmac] } # WARNING: when adding a new key, you may also want to add it in app/policies/setting_policy.rb#public_whitelist def value diff --git a/app/themes/casemate/style.scss.erb b/app/themes/casemate/style.scss.erb index 0caae8bc3..a4326dd4b 100644 --- a/app/themes/casemate/style.scss.erb +++ b/app/themes/casemate/style.scss.erb @@ -349,3 +349,10 @@ section#cookies-modal div.cookies-consent .cookies-actions button.accept { } } } + +.payzen-keys-form { + fieldset > legend { + background-color: $secondary; + color: $secondary-text-color; + } +} diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index d5e5767ea..b6d068665 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -638,6 +638,13 @@ en: error_check_keys: "Error: please check your Stripe keys." stripe_keys_saved: "Stripe keys successfully saved." error_saving_stripe_keys: "Unable to save the Stripe keys. Please try again later." + 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 keys" + 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." diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index fcdb2e22d..a29014072 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -638,6 +638,13 @@ fr: error_check_keys: "Erreur : veuillez vérifier vos clefs Stripe." stripe_keys_saved: "Les clefs Stripe ont bien été enregistrées." error_saving_stripe_keys: "Impossible d’enregistrer les clefs Stripe. Veuillez réessayer ultérieurement." + 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: "Clefs 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."