1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-01-18 07:52:23 +01:00

save & retreive stripe settings

This commit is contained in:
Sylvain 2021-03-30 15:56:36 +02:00
parent 720328ee92
commit c25e54a6af
13 changed files with 136 additions and 31 deletions

View File

@ -27,8 +27,8 @@ class API::PlansController < API::ApiController
partner = params[:plan][:partner_id].empty? ? nil : User.find(params[:plan][:partner_id])
res = PlansService.create(type, partner, plan_params)
if res[:errors]
render json: res[:errors], status: :unprocessable_entity
if res.errors
render json: res.errors, status: :unprocessable_entity
else
render json: res, status: :created
end

View File

@ -32,8 +32,11 @@ class API::SettingsController < API::ApiController
db_setting = Setting.find_or_initialize_by(name: setting[:name])
next unless SettingService.before_update(db_setting)
db_setting.save && db_setting.history_values.create(value: setting[:value], invoicing_profile: current_user.invoicing_profile)
SettingService.after_update(db_setting)
if db_setting.save
db_setting.history_values.create(value: setting[:value], invoicing_profile: current_user.invoicing_profile)
SettingService.after_update(db_setting)
end
@settings.push db_setting
end
end

View File

@ -6,7 +6,6 @@ import {
PaymentSchedule,
PaymentScheduleIndexRequest, RefreshItemResponse
} from '../models/payment-schedule';
import wrapPromise, { IWrapPromise } from '../lib/wrap-promise';
export default class PaymentScheduleAPI {
async list (query: PaymentScheduleIndexRequest): Promise<Array<PaymentSchedule>> {

View File

@ -1,6 +1,6 @@
import apiClient from './api-client';
import { AxiosResponse } from 'axios';
import { Setting, SettingName } from '../models/setting';
import { Setting, SettingBulkResult, SettingError, SettingName } from '../models/setting';
import wrapPromise, { IWrapPromise } from '../lib/wrap-promise';
export default class SettingAPI {
@ -10,10 +10,18 @@ export default class SettingAPI {
}
async query (names: Array<SettingName>): Promise<Map<SettingName, any>> {
const res: AxiosResponse = await apiClient.get(`/api/settings/?names=[${names.join(',')}]`);
const params = new URLSearchParams();
params.append('names', `['${names.join("','")}']`);
const res: AxiosResponse = await apiClient.get(`/api/settings?${params.toString()}`);
return SettingAPI.toSettingsMap(res?.data);
}
async bulkUpdate (settings: Map<SettingName, any>): Promise<Map<SettingName, SettingBulkResult>> {
const res: AxiosResponse = await apiClient.patch('/api/settings/bulk_update', { settings: SettingAPI.toObjectArray(settings) });
return SettingAPI.toBulkMap(res?.data?.settings);
}
static get (name: SettingName): IWrapPromise<Setting> {
const api = new SettingAPI();
return wrapPromise(api.get(name));
@ -24,15 +32,41 @@ export default class SettingAPI {
return wrapPromise(api.query(names));
}
private
static toSettingsMap(data: Object): Map<SettingName, any> {
private static toSettingsMap(data: Object): Map<SettingName, any> {
const dataArray: Array<Array<string | any>> = Object.entries(data);
const map = new Map();
dataArray.forEach(item => {
map.set(SettingName[item[0]], item[1]);
map.set(item[0] as SettingName, item[1]);
});
return map;
}
private static toBulkMap(data: Array<Setting|SettingError>): Map<SettingName, SettingBulkResult> {
const map = new Map();
data.forEach(item => {
const itemData: SettingBulkResult = { status: true };
if ('error' in item) {
itemData.error = item.error;
itemData.status = false;
}
if ('value' in item) {
itemData.value = item.value;
}
map.set(item.name as SettingName, itemData)
});
return map;
}
private static toObjectArray(data: Map<SettingName, any>): Array<Object> {
const array = [];
data.forEach((value, key) => {
array.push({
name: key,
value
})
});
return array;
}
}

View File

@ -2,12 +2,15 @@
* This component is a template for an input component that wraps the application style
*/
import React, { BaseSyntheticEvent, ReactNode, useCallback, useState } from 'react';
import React, { BaseSyntheticEvent, ReactNode, useCallback, useEffect, useState } from 'react';
import { debounce as _debounce } from 'lodash';
import SettingAPI from '../api/setting';
import { SettingName } from '../models/setting';
import { loadStripe } from '@stripe/stripe-js';
interface FabInputProps {
id: string,
onChange?: (event: BaseSyntheticEvent) => void,
onChange?: (value: any) => void,
value: any,
icon?: ReactNode,
addOn?: ReactNode,
@ -19,10 +22,14 @@ interface FabInputProps {
type?: 'text' | 'date' | 'password' | 'url' | 'time' | 'tel' | 'search' | 'number' | 'month' | 'email' | 'datetime-local' | 'week',
}
export const FabInput: React.FC<FabInputProps> = ({ id, onChange, value, icon, className, disabled, type, required, debounce, addOn, addOnClassName }) => {
const [inputValue, setInputValue] = useState<any>(value);
useEffect(() => {
setInputValue(value);
onChange(value);
}, [value]);
/**
* Check if the current component was provided an icon to display
*/
@ -46,12 +53,13 @@ export const FabInput: React.FC<FabInputProps> = ({ id, onChange, value, icon, c
* Handle the action of the button
*/
const handleChange = (e: BaseSyntheticEvent): void => {
setInputValue(e.target.value);
const newValue = e.target.value;
setInputValue(newValue);
if (typeof onChange === 'function') {
if (debounce) {
handler(e);
handler(newValue);
} else {
onChange(e);
onChange(newValue);
}
}
}

View File

@ -3,7 +3,7 @@
* The configuration of a payment gateway is required to enable the online payments.
*/
import React, { BaseSyntheticEvent, useState } from 'react';
import React, { BaseSyntheticEvent, useEffect, useState } from 'react';
import { react2angular } from 'react2angular';
import { Loader } from './loader';
import { IApplication } from '../models/application';
@ -12,7 +12,8 @@ import { FabModal, ModalSize } from './fab-modal';
import { User } from '../models/user';
import { Gateway } from '../models/gateway';
import { StripeKeysForm } from './stripe-keys-form';
import { SettingName } from '../models/setting';
import { SettingBulkResult, SettingName } from '../models/setting';
import SettingAPI from '../api/setting';
declare var Application: IApplication;
@ -21,22 +22,31 @@ interface SelectGatewayModalModalProps {
isOpen: boolean,
toggleModal: () => void,
currentUser: User,
onError: (errors: Map<SettingName, SettingBulkResult>|any) => void,
onSuccess: (results: Map<SettingName, SettingBulkResult>) => void,
}
const SelectGatewayModal: React.FC<SelectGatewayModalModalProps> = ({ isOpen, toggleModal }) => {
const paymentGateway = SettingAPI.get(SettingName.PaymentGateway);
const SelectGatewayModal: React.FC<SelectGatewayModalModalProps> = ({ isOpen, toggleModal, onError, onSuccess }) => {
const { t } = useTranslation('admin');
const [preventConfirmGateway, setPreventConfirmGateway] = useState<boolean>(true);
const [selectedGateway, setSelectedGateway] = useState<string>('');
const [gatewayConfig, setGatewayConfig] = useState<Map<SettingName, string>>(new Map());
useEffect(() => {
const gateway = paymentGateway.read();
setSelectedGateway(gateway.value);
}, []);
/**
* Callback triggered when the user has filled and confirmed the settings of his gateway
*/
const onGatewayConfirmed = () => {
setPreventConfirmGateway(true);
toggleModal();
updateSettings();
setPreventConfirmGateway(false);
}
/**
@ -67,6 +77,25 @@ const SelectGatewayModal: React.FC<SelectGatewayModalModalProps> = ({ isOpen, to
setPreventConfirmGateway(false);
}
/**
* Send the new gateway settings to the API to save them
*/
const updateSettings = (): void => {
const settings = new Map<SettingName, string>(gatewayConfig);
settings.set(SettingName.PaymentGateway, selectedGateway);
const api = new SettingAPI();
api.bulkUpdate(settings).then(result => {
if (Array.from(result.values()).filter(item => !item.status).length > 0) {
onError(result);
} else {
onSuccess(result);
}
}, reason => {
onError(reason);
});
}
return (
<FabModal title={t('app.admin.invoices.payment.gateway_modal.select_gateway_title')}
isOpen={isOpen}
@ -91,12 +120,12 @@ const SelectGatewayModal: React.FC<SelectGatewayModalModalProps> = ({ isOpen, to
);
};
const SelectGatewayModalWrapper: React.FC<SelectGatewayModalModalProps> = ({ isOpen, toggleModal, currentUser }) => {
const SelectGatewayModalWrapper: React.FC<SelectGatewayModalModalProps> = ({ isOpen, toggleModal, currentUser, onSuccess, onError }) => {
return (
<Loader>
<SelectGatewayModal isOpen={isOpen} toggleModal={toggleModal} currentUser={currentUser} />
<SelectGatewayModal isOpen={isOpen} toggleModal={toggleModal} currentUser={currentUser} onSuccess={onSuccess} onError={onError} />
</Loader>
);
}
Application.Components.component('selectGatewayModal', react2angular(SelectGatewayModalWrapper, ['isOpen', 'toggleModal', 'currentUser']));
Application.Components.component('selectGatewayModal', react2angular(SelectGatewayModalWrapper, ['isOpen', 'toggleModal', 'currentUser', 'onSuccess', 'onError']));

View File

@ -44,8 +44,7 @@ const StripeKeysFormComponent: React.FC<StripeKeysFormProps> = ({ onValidKeys })
/**
* Send a test call to the Stripe API to check if the inputted public key is valid
*/
const testPublicKey = (e: BaseSyntheticEvent) => {
const key = e.target.value;
const testPublicKey = (key: string) => {
if (!key.match(/^pk_/)) {
setPublicKeyAddOn(<i className="fa fa-times" />);
setPublicKeyAddOnClassName('key-invalid');
@ -66,8 +65,7 @@ const StripeKeysFormComponent: React.FC<StripeKeysFormProps> = ({ onValidKeys })
/**
* Send a test call to the Stripe API to check if the inputted secret key is valid
*/
const testSecretKey = (e: BaseSyntheticEvent) => {
const key = e.target.value;
const testSecretKey = (key: string) => {
if (!key.match(/^sk_/)) {
setSecretKeyAddOn(<i className="fa fa-times" />);
setSecretKeyAddOnClassName('key-invalid');

View File

@ -698,6 +698,21 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
}, 50);
};
/**
* Callback triggered after the gateway was successfully configured in the dedicated modal
*/
$scope.onGatewayModalSuccess = function (settings) {
$scope.toggleSelectGatewayModal();
};
/**
* Callback triggered after the gateway failed to be configured
*/
$scope.onGatewayModalError = function (errors) {
growl.error(_t('app.admin.invoices.payment.gateway_configuration_error'));
console.error(errors);
};
/**
* Setup the feature-tour for the admin/invoices page.
* This is intended as a contextual help (when pressing F1)

View File

@ -101,7 +101,8 @@ export enum SettingName {
UpcomingEventsShown = 'upcoming_events_shown',
PaymentSchedulePrefix = 'payment_schedule_prefix',
TrainingsModule = 'trainings_module',
AddressRequired = 'address_required'
AddressRequired = 'address_required',
PaymentGateway = 'payment_gateway'
}
export interface Setting {
@ -110,3 +111,15 @@ export interface Setting {
last_update: Date,
history: Array<HistoryValue>
}
export interface SettingError {
error: string,
id: number,
name: string
}
export interface SettingBulkResult {
status: boolean,
value?: any,
error?: string
}

View File

@ -13,7 +13,11 @@
on-before-save="selectPaymentGateway"
fa-icon="fa-font">
</boolean-setting>
<select-gateway-modal is-open="openSelectGatewayModal" toggle-modal="toggleSelectGatewayModal" current-user="currentUser" />
<select-gateway-modal is-open="openSelectGatewayModal"
toggle-modal="toggleSelectGatewayModal"
current-user="currentUser"
on-success="onGatewayModalSuccess"
on-error="onGatewayModalError" />
</div>
<div class="row m-t" ng-show="allSettings.online_payment_module === 'true'">
<h3 class="m-l" translate>{{ 'app.admin.invoices.payment.stripe_keys' }}</h3>

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
json.settings @settings.each do |setting|
if setting[:errors]
if setting.errors.keys.count.positive?
json.error setting.errors.full_messages
json.id setting.id
json.name setting.name

View File

@ -643,6 +643,7 @@ en:
currency_info_html: "Please specify below the currency used for online payment. You should provide a three-letter ISO code, from the list of <a href='https://stripe.com/docs/currencies' target='_blank'>Stripe supported currencies</a>."
currency_alert_html: "<strong>Warning</strong>: 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."
# select a payment gateway
gateway_modal:
select_gateway_title: "Select a payment gateway"

View File

@ -643,6 +643,7 @@ fr:
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 <a href='https://stripe.com/docs/currencies' target='_blank'>devises supportées par Stripe</a>."
currency_alert_html: "<strong>Attention</strong> : 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."
# select a payment gateway
gateway_modal:
select_gateway_title: "Sélectionnez une passerelle de paiement"