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:
parent
5e2c50a85f
commit
fe5c4e6233
@ -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 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 }) => {
|
export const AbstractPaymentModal: React.FC<AbstractPaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, cartItems, currentUser, schedule, customer, logoFooter, GatewayForm, formId, className, formClassName }) => {
|
||||||
// customer's wallet
|
// customer's wallet
|
||||||
|
@ -25,6 +25,10 @@ interface PaymentModalProps {
|
|||||||
// initial request to the API
|
// initial request to the API
|
||||||
const paymentGateway = SettingAPI.get(SettingName.PaymentGateway);
|
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 PaymentModal: React.FC<PaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, currentUser, schedule , cartItems, customer }) => {
|
||||||
const gateway = paymentGateway.read();
|
const gateway = paymentGateway.read();
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -20,9 +20,6 @@ const payZenSettings: Array<SettingName> = [SettingName.PayZenUsername, SettingN
|
|||||||
// settings related the to PayZen REST API (server side)
|
// settings related the to PayZen REST API (server side)
|
||||||
const restApiSettings: Array<SettingName> = [SettingName.PayZenUsername, SettingName.PayZenPassword, SettingName.PayZenEndpoint, SettingName.PayZenHmacKey];
|
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.
|
// Prevent multiples call to the payzen keys validation endpoint.
|
||||||
// this cannot be handled by a React state because of their asynchronous nature
|
// this cannot be handled by a React state because of their asynchronous nature
|
||||||
let pendingKeysValidation = false;
|
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)
|
* When the component loads for the first time, initialize the keys with the values fetched from the API (if any)
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateSettings(new Map(payZenKeys.read()));
|
const api = new SettingAPI();
|
||||||
|
api.query(payZenSettings).then(payZenKeys => {
|
||||||
|
updateSettings(new Map(payZenKeys));
|
||||||
|
}).catch(error => console.error(error));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
import React, { FunctionComponent, ReactNode } from 'react';
|
import React, { FunctionComponent, ReactNode } from 'react';
|
||||||
import { react2angular } from 'react2angular';
|
|
||||||
import { GatewayFormProps, AbstractPaymentModal } from '../abstract-payment-modal';
|
import { GatewayFormProps, AbstractPaymentModal } from '../abstract-payment-modal';
|
||||||
import { Loader } from '../../base/loader';
|
|
||||||
import { IApplication } from '../../../models/application';
|
|
||||||
import { CartItems, PaymentConfirmation } from '../../../models/payment';
|
import { CartItems, PaymentConfirmation } from '../../../models/payment';
|
||||||
import { PaymentSchedule } from '../../../models/payment-schedule';
|
import { PaymentSchedule } from '../../../models/payment-schedule';
|
||||||
import { User } from '../../../models/user';
|
import { User } from '../../../models/user';
|
||||||
@ -10,8 +7,8 @@ import { User } from '../../../models/user';
|
|||||||
import payzenLogo from '../../../../../images/payzen-secure.png';
|
import payzenLogo from '../../../../../images/payzen-secure.png';
|
||||||
import mastercardLogo from '../../../../../images/mastercard.png';
|
import mastercardLogo from '../../../../../images/mastercard.png';
|
||||||
import visaLogo from '../../../../../images/visa.png';
|
import visaLogo from '../../../../../images/visa.png';
|
||||||
|
import { PayzenForm } from './payzen-form';
|
||||||
|
|
||||||
declare var Application: IApplication;
|
|
||||||
|
|
||||||
interface PayZenModalProps {
|
interface PayZenModalProps {
|
||||||
isOpen: boolean,
|
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.
|
* This component enables the user to input his card data or process payments, using the PayZen gateway.
|
||||||
* Supports Strong-Customer Authentication (SCA).
|
* 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 }) => {
|
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}) => {
|
const renderForm: FunctionComponent<GatewayFormProps> = ({ onSubmit, onSuccess, onError, operator, className, formId, cartItems, customer, paymentSchedule, children}) => {
|
||||||
return (
|
return (
|
||||||
<form onSubmit={onSubmit} className={className} id={formId}>
|
<PayzenForm onSubmit={onSubmit}
|
||||||
<h3>PayZen</h3>
|
onSuccess={onSuccess}
|
||||||
<input type="text" placeholder="card #"/>
|
onError={onError}
|
||||||
<span>Operated by {operator.name}</span>
|
customer={customer}
|
||||||
<span>User: {customer.name}</span>
|
operator={operator}
|
||||||
|
formId={formId}
|
||||||
|
className={className}
|
||||||
|
paymentSchedule={paymentSchedule}>
|
||||||
{children}
|
{children}
|
||||||
</form>
|
</PayzenForm>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,13 +38,6 @@ const icons:Map<SettingName, string> = new Map([
|
|||||||
[SettingName.PayZenPublicKey, 'info']
|
[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
|
* 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.
|
* For the private settings, we initialize them with the placeholder value, if the setting is set.
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const map = new Map(payZenKeys.read());
|
const api = new SettingAPI();
|
||||||
for (const setting of payZenPrivateSettings) {
|
api.query(payZenPublicSettings.concat(payZenOtherSettings)).then(payZenKeys => {
|
||||||
map.set(setting, isPresent[setting].read() ? PAYZEN_HIDDEN : '');
|
api.isPresent(SettingName.PayZenPassword).then(pzPassword => {
|
||||||
}
|
api.isPresent(SettingName.PayZenHmacKey).then(pzHmac => {
|
||||||
updateSettings(map);
|
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); });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
@ -139,7 +139,7 @@ export const StripeForm: React.FC<StripeFormProps> = ({ onSubmit, onSuccess, onE
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit} id={formId} className={className}>
|
<form onSubmit={handleSubmit} id={formId} className={className ? className : ''}>
|
||||||
<CardElement options={cardOptions} />
|
<CardElement options={cardOptions} />
|
||||||
{children}
|
{children}
|
||||||
</form>
|
</form>
|
||||||
|
@ -12,9 +12,6 @@ interface StripeKeysFormProps {
|
|||||||
onValidKeys: (stripePublic: string, stripeSecret:string) => void
|
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
|
* Form to set the stripe's public and private keys
|
||||||
*/
|
*/
|
||||||
@ -44,9 +41,12 @@ const StripeKeysFormComponent: React.FC<StripeKeysFormProps> = ({ onValidKeys })
|
|||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
mounted.current = true;
|
mounted.current = true;
|
||||||
const keys = stripeKeys.read();
|
|
||||||
setPublicKey(keys.get(SettingName.StripePublicKey));
|
const api = new SettingAPI();
|
||||||
setSecretKey(keys.get(SettingName.StripeSecretKey));
|
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
|
// when the component unmounts, mark it as unmounted
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
import React, { FunctionComponent, ReactNode } from 'react';
|
import React, { FunctionComponent, ReactNode } from 'react';
|
||||||
import { react2angular } from 'react2angular';
|
|
||||||
import { SetupIntent } from '@stripe/stripe-js';
|
import { SetupIntent } from '@stripe/stripe-js';
|
||||||
import { StripeElements } from './stripe-elements';
|
import { StripeElements } from './stripe-elements';
|
||||||
import { StripeForm } from './stripe-form';
|
import { StripeForm } from './stripe-form';
|
||||||
import { GatewayFormProps, AbstractPaymentModal } from '../abstract-payment-modal';
|
import { GatewayFormProps, AbstractPaymentModal } from '../abstract-payment-modal';
|
||||||
import { Loader } from '../../base/loader';
|
|
||||||
import { IApplication } from '../../../models/application';
|
|
||||||
import { CartItems, PaymentConfirmation } from '../../../models/payment';
|
import { CartItems, PaymentConfirmation } from '../../../models/payment';
|
||||||
import { PaymentSchedule } from '../../../models/payment-schedule';
|
import { PaymentSchedule } from '../../../models/payment-schedule';
|
||||||
import { User } from '../../../models/user';
|
import { User } from '../../../models/user';
|
||||||
@ -14,7 +11,6 @@ import stripeLogo from '../../../../../images/powered_by_stripe.png';
|
|||||||
import mastercardLogo from '../../../../../images/mastercard.png';
|
import mastercardLogo from '../../../../../images/mastercard.png';
|
||||||
import visaLogo from '../../../../../images/visa.png';
|
import visaLogo from '../../../../../images/visa.png';
|
||||||
|
|
||||||
declare var Application: IApplication;
|
|
||||||
|
|
||||||
interface StripeModalProps {
|
interface StripeModalProps {
|
||||||
isOpen: boolean,
|
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.
|
* This component enables the user to input his card data or process payments, using the Stripe gateway.
|
||||||
* Supports Strong-Customer Authentication (SCA).
|
* 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 }) => {
|
export const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuccess, cartItems, currentUser, schedule, customer }) => {
|
||||||
/**
|
/**
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
.payzen-modal {
|
.payzen-modal {
|
||||||
|
.payzen-form {
|
||||||
|
.container {
|
||||||
|
display: flex; justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
.payzen-modal-icons {
|
.payzen-modal-icons {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
@ -38,7 +38,8 @@ class SettingPolicy < ApplicationPolicy
|
|||||||
fablab_name name_genre event_explications_alert space_explications_alert link_name home_content phone_required
|
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
|
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
|
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
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
|
Loading…
Reference in New Issue
Block a user