1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-12-01 12:24:28 +01:00

integrate the payzen form widget into the modal

Also: do not fetch the api from unmounted components
This commit is contained in:
Sylvain 2021-04-09 12:09:54 +02:00
parent 5e2c50a85f
commit fe5c4e6233
11 changed files with 130 additions and 38 deletions

View File

@ -49,6 +49,9 @@ const cgvFile = CustomAssetAPI.get(CustomAssetName.CgvFile);
/**
* This component is an abstract modal that must be extended by each payment gateway to include its payment form.
*
* This component must not be called directly but must be extended for each implemented payment gateway
* @see https://reactjs.org/docs/composition-vs-inheritance.html
*/
export const AbstractPaymentModal: React.FC<AbstractPaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, cartItems, currentUser, schedule, customer, logoFooter, GatewayForm, formId, className, formClassName }) => {
// customer's wallet

View File

@ -25,6 +25,10 @@ interface PaymentModalProps {
// initial request to the API
const paymentGateway = SettingAPI.get(SettingName.PaymentGateway);
/**
* This component open a modal dialog for the configured payment gateway, allowing the user to input his card data
* to process an online payment.
*/
const PaymentModal: React.FC<PaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, currentUser, schedule , cartItems, customer }) => {
const gateway = paymentGateway.read();

View File

@ -0,0 +1,77 @@
import React, { FormEvent, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import KRGlue from "@lyracom/embedded-form-glue";
import { CartItems } from '../../../models/payment';
import { User } from '../../../models/user';
import SettingAPI from '../../../api/setting';
import { SettingName } from '../../../models/setting';
interface PayzenFormProps {
onSubmit: () => void,
onSuccess: (result: any) => void,
onError: (message: string) => void,
customer: User,
operator: User,
className?: string,
paymentSchedule?: boolean,
cartItems?: CartItems,
formId: string,
}
/**
* A form component to collect the credit card details and to create the payment method on Stripe.
* The form validation button must be created elsewhere, using the attribute form={formId}.
*/
export const PayzenForm: React.FC<PayzenFormProps> = ({ onSubmit, onSuccess, onError, children, className, paymentSchedule = false, cartItems, customer, operator, formId }) => {
const { t } = useTranslation('shared');
useEffect(() => {
const api = new SettingAPI();
api.query([SettingName.PayZenEndpoint, SettingName.PayZenPublicKey]).then(settings => {
const formToken = "DEMO-TOKEN-TO-BE-REPLACED";
KRGlue.loadLibrary(settings.get(SettingName.PayZenEndpoint), settings.get(SettingName.PayZenPublicKey)) /* Load the remote library */
.then(({ KR }) =>
KR.setFormConfig({
/* set the minimal configuration */
formToken: formToken,
"kr-language": "en-US" /* to update initialization parameter */
})
)
.then(({ KR }) =>
KR.addForm("#payzenPaymentForm")
) /* add a payment form to myPaymentForm div*/
.then(({ KR, result }) =>
KR.showForm(result.formId)
); /* show the payment form */
}).catch(error => console.error(error));
});
/**
* Handle the submission of the form.
*/
const handleSubmit = async (event: FormEvent): Promise<void> => {
event.preventDefault();
onSubmit();
try {
onSuccess(null);
} catch (err) {
// catch api errors
onError(err);
}
}
return (
<form onSubmit={handleSubmit} id={formId} className={className ? className : ''}>
<div className="container">
<div id="payzenPaymentForm" />
</div>
{children}
</form>
);
}

View File

@ -20,9 +20,6 @@ const payZenSettings: Array<SettingName> = [SettingName.PayZenUsername, SettingN
// settings related the to PayZen REST API (server side)
const restApiSettings: Array<SettingName> = [SettingName.PayZenUsername, SettingName.PayZenPassword, SettingName.PayZenEndpoint, SettingName.PayZenHmacKey];
// initial request to the API
const payZenKeys = SettingAPI.query(payZenSettings);
// Prevent multiples call to the payzen keys validation endpoint.
// this cannot be handled by a React state because of their asynchronous nature
let pendingKeysValidation = false;
@ -48,7 +45,10 @@ const PayZenKeysFormComponent: React.FC<PayZenKeysFormProps> = ({ onValidKeys })
* When the component loads for the first time, initialize the keys with the values fetched from the API (if any)
*/
useEffect(() => {
updateSettings(new Map(payZenKeys.read()));
const api = new SettingAPI();
api.query(payZenSettings).then(payZenKeys => {
updateSettings(new Map(payZenKeys));
}).catch(error => console.error(error));
}, []);
/**

View File

@ -1,8 +1,5 @@
import React, { FunctionComponent, ReactNode } from 'react';
import { react2angular } from 'react2angular';
import { GatewayFormProps, AbstractPaymentModal } from '../abstract-payment-modal';
import { Loader } from '../../base/loader';
import { IApplication } from '../../../models/application';
import { CartItems, PaymentConfirmation } from '../../../models/payment';
import { PaymentSchedule } from '../../../models/payment-schedule';
import { User } from '../../../models/user';
@ -10,8 +7,8 @@ import { User } from '../../../models/user';
import payzenLogo from '../../../../../images/payzen-secure.png';
import mastercardLogo from '../../../../../images/mastercard.png';
import visaLogo from '../../../../../images/visa.png';
import { PayzenForm } from './payzen-form';
declare var Application: IApplication;
interface PayZenModalProps {
isOpen: boolean,
@ -26,6 +23,9 @@ interface PayZenModalProps {
/**
* This component enables the user to input his card data or process payments, using the PayZen gateway.
* Supports Strong-Customer Authentication (SCA).
*
* This component should not be called directly. Prefer using <PaymentModal> which can handle the configuration
* of a different payment gateway.
*/
export const PayZenModal: React.FC<PayZenModalProps> = ({ isOpen, toggleModal, afterSuccess, cartItems, currentUser, schedule, customer }) => {
/**
@ -46,13 +46,16 @@ export const PayZenModal: React.FC<PayZenModalProps> = ({ isOpen, toggleModal, a
*/
const renderForm: FunctionComponent<GatewayFormProps> = ({ onSubmit, onSuccess, onError, operator, className, formId, cartItems, customer, paymentSchedule, children}) => {
return (
<form onSubmit={onSubmit} className={className} id={formId}>
<h3>PayZen</h3>
<input type="text" placeholder="card #"/>
<span>Operated by {operator.name}</span>
<span>User: {customer.name}</span>
<PayzenForm onSubmit={onSubmit}
onSuccess={onSuccess}
onError={onError}
customer={customer}
operator={operator}
formId={formId}
className={className}
paymentSchedule={paymentSchedule}>
{children}
</form>
</PayzenForm>
);
}

View File

@ -38,13 +38,6 @@ const icons:Map<SettingName, string> = new Map([
[SettingName.PayZenPublicKey, 'info']
])
// initial requests to the API
const payZenKeys = SettingAPI.query(payZenPublicSettings.concat(payZenOtherSettings));
const isPresent = {
[SettingName.PayZenPassword]: SettingAPI.isPresent(SettingName.PayZenPassword),
[SettingName.PayZenHmacKey]: SettingAPI.isPresent(SettingName.PayZenHmacKey)
};
/**
* This component displays a summary of the PayZen account keys, with a button triggering the modal to edit them
*/
@ -61,11 +54,18 @@ export const PayzenSettings: React.FC<PayzenSettingsProps> = ({ onEditKeys, onCu
* For the private settings, we initialize them with the placeholder value, if the setting is set.
*/
useEffect(() => {
const map = new Map(payZenKeys.read());
for (const setting of payZenPrivateSettings) {
map.set(setting, isPresent[setting].read() ? PAYZEN_HIDDEN : '');
}
updateSettings(map);
const api = new SettingAPI();
api.query(payZenPublicSettings.concat(payZenOtherSettings)).then(payZenKeys => {
api.isPresent(SettingName.PayZenPassword).then(pzPassword => {
api.isPresent(SettingName.PayZenHmacKey).then(pzHmac => {
const map = new Map(payZenKeys);
map.set(SettingName.PayZenPassword, pzPassword ? PAYZEN_HIDDEN : '');
map.set(SettingName.PayZenHmacKey, pzHmac ? PAYZEN_HIDDEN : '');
updateSettings(map);
}).catch(error => { console.error(error); })
}).catch(error => { console.error(error); });
}).catch(error => { console.error(error); });
}, []);

View File

@ -139,7 +139,7 @@ export const StripeForm: React.FC<StripeFormProps> = ({ onSubmit, onSuccess, onE
};
return (
<form onSubmit={handleSubmit} id={formId} className={className}>
<form onSubmit={handleSubmit} id={formId} className={className ? className : ''}>
<CardElement options={cardOptions} />
{children}
</form>

View File

@ -12,9 +12,6 @@ interface StripeKeysFormProps {
onValidKeys: (stripePublic: string, stripeSecret:string) => void
}
// initial request to the API
const stripeKeys = SettingAPI.query([SettingName.StripePublicKey, SettingName.StripeSecretKey]);
/**
* Form to set the stripe's public and private keys
*/
@ -44,9 +41,12 @@ const StripeKeysFormComponent: React.FC<StripeKeysFormProps> = ({ onValidKeys })
*/
useEffect(() => {
mounted.current = true;
const keys = stripeKeys.read();
setPublicKey(keys.get(SettingName.StripePublicKey));
setSecretKey(keys.get(SettingName.StripeSecretKey));
const api = new SettingAPI();
api.query([SettingName.StripePublicKey, SettingName.StripeSecretKey]).then(stripeKeys => {
setPublicKey(stripeKeys.get(SettingName.StripePublicKey));
setSecretKey(stripeKeys.get(SettingName.StripeSecretKey));
}).catch(error => console.error(error));
// when the component unmounts, mark it as unmounted
return () => {

View File

@ -1,11 +1,8 @@
import React, { FunctionComponent, ReactNode } from 'react';
import { react2angular } from 'react2angular';
import { SetupIntent } from '@stripe/stripe-js';
import { StripeElements } from './stripe-elements';
import { StripeForm } from './stripe-form';
import { GatewayFormProps, AbstractPaymentModal } from '../abstract-payment-modal';
import { Loader } from '../../base/loader';
import { IApplication } from '../../../models/application';
import { CartItems, PaymentConfirmation } from '../../../models/payment';
import { PaymentSchedule } from '../../../models/payment-schedule';
import { User } from '../../../models/user';
@ -14,7 +11,6 @@ import stripeLogo from '../../../../../images/powered_by_stripe.png';
import mastercardLogo from '../../../../../images/mastercard.png';
import visaLogo from '../../../../../images/visa.png';
declare var Application: IApplication;
interface StripeModalProps {
isOpen: boolean,
@ -29,6 +25,9 @@ interface StripeModalProps {
/**
* This component enables the user to input his card data or process payments, using the Stripe gateway.
* Supports Strong-Customer Authentication (SCA).
*
* This component should not be called directly. Prefer using <PaymentModal> which can handle the configuration
* of a different payment gateway.
*/
export const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuccess, cartItems, currentUser, schedule, customer }) => {
/**

View File

@ -1,4 +1,9 @@
.payzen-modal {
.payzen-form {
.container {
display: flex; justify-content: center;
}
}
.payzen-modal-icons {
text-align: center;

View File

@ -38,7 +38,8 @@ class SettingPolicy < ApplicationPolicy
fablab_name name_genre event_explications_alert space_explications_alert link_name home_content phone_required
tracking_id book_overlapping_slots slot_duration events_in_calendar spaces_module plans_module invoicing_module
recaptcha_site_key feature_tour_display disqus_shortname allowed_cad_extensions openlab_app_id openlab_default
online_payment_module stripe_public_key confirmation_required wallet_module trainings_module address_required payment_gateway]
online_payment_module stripe_public_key confirmation_required wallet_module trainings_module address_required
payment_gateway payzen_endpoint payzen_public_key]
end
##