mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-17 06:52:27 +01:00
stripe modal -> generic payment modal
This commit is contained in:
parent
d091c44c11
commit
d70d0dcf7a
BIN
app/frontend/images/payzen-secure.png
Normal file
BIN
app/frontend/images/payzen-secure.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
209
app/frontend/src/javascript/components/base/payment-modal.tsx
Normal file
209
app/frontend/src/javascript/components/base/payment-modal.tsx
Normal file
@ -0,0 +1,209 @@
|
||||
import React, { FunctionComponent, ReactNode, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import WalletLib from '../../lib/wallet';
|
||||
import { WalletInfo } from '../wallet-info';
|
||||
import { FabModal, ModalSize } from './fab-modal';
|
||||
import { HtmlTranslate } from './html-translate';
|
||||
import { CustomAssetName } from '../../models/custom-asset';
|
||||
import { IFablab } from '../../models/fablab';
|
||||
import { CartItems } from '../../models/payment';
|
||||
import { PaymentSchedule } from '../../models/payment-schedule';
|
||||
import { User } from '../../models/user';
|
||||
import CustomAssetAPI from '../../api/custom-asset';
|
||||
import PriceAPI from '../../api/price';
|
||||
import WalletAPI from '../../api/wallet';
|
||||
|
||||
declare var Fablab: IFablab;
|
||||
|
||||
|
||||
export interface GatewayFormProps {
|
||||
onSubmit: () => void,
|
||||
onSuccess: (result: any) => void,
|
||||
onError: (message: string) => void,
|
||||
customer: User,
|
||||
operator: User,
|
||||
className?: string,
|
||||
paymentSchedule?: boolean,
|
||||
cartItems?: CartItems,
|
||||
formId: string,
|
||||
}
|
||||
|
||||
interface PaymentModalProps {
|
||||
isOpen: boolean,
|
||||
toggleModal: () => void,
|
||||
afterSuccess: (result: any) => void,
|
||||
cartItems: CartItems,
|
||||
currentUser: User,
|
||||
schedule: PaymentSchedule,
|
||||
customer: User,
|
||||
logoFooter: ReactNode,
|
||||
GatewayForm: FunctionComponent<GatewayFormProps>,
|
||||
formId: string,
|
||||
className?: string,
|
||||
formClassName?: string,
|
||||
}
|
||||
|
||||
|
||||
// initial request to the API
|
||||
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.
|
||||
*/
|
||||
export const PaymentModal: React.FC<PaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, cartItems, currentUser, schedule, customer, logoFooter, GatewayForm, formId, className, formClassName }) => {
|
||||
// customer's wallet
|
||||
const [wallet, setWallet] = useState(null);
|
||||
// server-computed price with all details
|
||||
const [price, setPrice] = useState(null);
|
||||
// remaining price = total price - wallet amount
|
||||
const [remainingPrice, setRemainingPrice] = useState(0);
|
||||
// is the component ready to display?
|
||||
const [ready, setReady] = useState(false);
|
||||
// errors to display in the UI (gateway errors mainly)
|
||||
const [errors, setErrors] = useState(null);
|
||||
// are we currently processing the payment (ie. the form was submit, but the process is still running)?
|
||||
const [submitState, setSubmitState] = useState(false);
|
||||
// did the user accepts the terms of services (CGV)?
|
||||
const [tos, setTos] = useState(false);
|
||||
|
||||
const { t } = useTranslation('shared');
|
||||
const cgv = cgvFile.read();
|
||||
|
||||
|
||||
/**
|
||||
* On each display:
|
||||
* - Refresh the wallet
|
||||
* - Refresh the price
|
||||
* - Refresh the remaining price
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!cartItems) return;
|
||||
WalletAPI.getByUser(cartItems.reservation?.user_id || cartItems.subscription?.user_id).then((wallet) => {
|
||||
setWallet(wallet);
|
||||
PriceAPI.compute(cartItems).then((res) => {
|
||||
setPrice(res);
|
||||
const wLib = new WalletLib(wallet);
|
||||
setRemainingPrice(wLib.computeRemainingPrice(res.price));
|
||||
setReady(true);
|
||||
})
|
||||
})
|
||||
}, [cartItems]);
|
||||
|
||||
/**
|
||||
* Check if there is currently an error to display
|
||||
*/
|
||||
const hasErrors = (): boolean => {
|
||||
return errors !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user accepts the Terms of Sales document
|
||||
*/
|
||||
const hasCgv = (): boolean => {
|
||||
return cgv != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when the user accepts or declines the Terms of Sales
|
||||
*/
|
||||
const toggleTos = (): void => {
|
||||
setTos(!tos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we are currently creating a payment schedule
|
||||
*/
|
||||
const isPaymentSchedule = (): boolean => {
|
||||
return schedule !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the formatted localized amount for the given price (eg. 20.5 => "20,50 €")
|
||||
*/
|
||||
const formatPrice = (amount: number): string => {
|
||||
return new Intl.NumberFormat(Fablab.intl_locale, {style: 'currency', currency: Fablab.intl_currency}).format(amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the component as 'currently submitting'
|
||||
*/
|
||||
const handleSubmit = (): void => {
|
||||
setSubmitState(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* After sending the form with success, process the resulting payment method
|
||||
*/
|
||||
const handleFormSuccess = async (result: any): Promise<void> => {
|
||||
setSubmitState(false);
|
||||
afterSuccess(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the payment form raises an error, it is handled by this callback which display it in the modal.
|
||||
*/
|
||||
const handleFormError = (message: string): void => {
|
||||
setSubmitState(false);
|
||||
setErrors(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the form can be submitted.
|
||||
* => We're not currently already submitting the form, and, if the terms of service are enabled, the user must agree with them.
|
||||
*/
|
||||
const canSubmit = (): boolean => {
|
||||
let terms = true;
|
||||
if (hasCgv()) { terms = tos; }
|
||||
return !submitState && terms;
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<FabModal title={t('app.shared.payment.online_payment') }
|
||||
isOpen={isOpen}
|
||||
toggleModal={toggleModal}
|
||||
width={ModalSize.medium}
|
||||
closeButton={false}
|
||||
customFooter={logoFooter}
|
||||
className={`payment-modal ${className}`}>
|
||||
{ready && <div>
|
||||
<WalletInfo cartItems={cartItems} currentUser={currentUser} wallet={wallet} price={price?.price} />
|
||||
<GatewayForm onSubmit={handleSubmit}
|
||||
onSuccess={handleFormSuccess}
|
||||
onError={handleFormError}
|
||||
operator={currentUser}
|
||||
className={`gateway-form ${formClassName}`}
|
||||
formId={formId}
|
||||
cartItems={cartItems}
|
||||
customer={customer}
|
||||
paymentSchedule={isPaymentSchedule()}>
|
||||
{hasErrors() && <div className="payment-errors">
|
||||
{errors}
|
||||
</div>}
|
||||
{isPaymentSchedule() && <div className="payment-schedule-info">
|
||||
<HtmlTranslate trKey="app.shared.payment.payment_schedule_html" options={{ DEADLINES: schedule.items.length }} />
|
||||
</div>}
|
||||
{hasCgv() && <div className="terms-of-sales">
|
||||
<input type="checkbox" id="acceptToS" name="acceptCondition" checked={tos} onChange={toggleTos} required />
|
||||
<label htmlFor="acceptToS">{ t('app.shared.payment.i_have_read_and_accept_') }
|
||||
<a href={cgv.custom_asset_file_attributes.attachment_url} target="_blank">
|
||||
{ t('app.shared.payment._the_general_terms_and_conditions') }
|
||||
</a>
|
||||
</label>
|
||||
</div>}
|
||||
</GatewayForm>
|
||||
{!submitState && <button type="submit"
|
||||
disabled={!canSubmit()}
|
||||
form={formId}
|
||||
className="validate-btn">
|
||||
{t('app.shared.payment.confirm_payment_of_', { AMOUNT: formatPrice(remainingPrice) })}
|
||||
</button>}
|
||||
{submitState && <div className="payment-pending">
|
||||
<div className="fa-2x">
|
||||
<i className="fas fa-circle-notch fa-spin" />
|
||||
</div>
|
||||
</div>}
|
||||
</div>}
|
||||
</FabModal>
|
||||
);
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
import React, { FunctionComponent, ReactNode } from 'react';
|
||||
import { react2angular } from 'react2angular';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
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';
|
||||
|
||||
import payzenLogo from '../../../../images/payzen-secure.png';
|
||||
import mastercardLogo from '../../../../images/mastercard.png';
|
||||
import visaLogo from '../../../../images/visa.png';
|
||||
import { GatewayFormProps, PaymentModal } from '../base/payment-modal';
|
||||
|
||||
declare var Application: IApplication;
|
||||
|
||||
interface PayZenModalProps {
|
||||
isOpen: boolean,
|
||||
toggleModal: () => void,
|
||||
afterSuccess: (result: PaymentConfirmation) => void,
|
||||
cartItems: CartItems,
|
||||
currentUser: User,
|
||||
schedule: PaymentSchedule,
|
||||
customer: User
|
||||
}
|
||||
|
||||
/**
|
||||
* This component enables the user to input his card data or process payments, using the PayZen gateway.
|
||||
* Supports Strong-Customer Authentication (SCA).
|
||||
*/
|
||||
const PayZenModal: React.FC<PayZenModalProps> = ({ isOpen, toggleModal, afterSuccess, cartItems, currentUser, schedule, customer }) => {
|
||||
/**
|
||||
* Return the logos, shown in the modal footer.
|
||||
*/
|
||||
const logoFooter = (): ReactNode => {
|
||||
return (
|
||||
<div className="payzen-modal-icons">
|
||||
<i className="fa fa-lock fa-2x m-r-sm pos-rlt" />
|
||||
<img src={payzenLogo} alt="powered by PayZen" />
|
||||
<img src={mastercardLogo} alt="mastercard" />
|
||||
<img src={visaLogo} alt="visa" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Integrates the PayzenForm into the parent PaymentModal
|
||||
*/
|
||||
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>
|
||||
{children}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PaymentModal isOpen={isOpen}
|
||||
toggleModal={toggleModal}
|
||||
logoFooter={logoFooter()}
|
||||
formId="payzen-modal"
|
||||
currentUser={currentUser}
|
||||
cartItems={cartItems}
|
||||
customer={customer}
|
||||
afterSuccess={afterSuccess}
|
||||
schedule={schedule}
|
||||
GatewayForm={renderForm} />
|
||||
);
|
||||
}
|
||||
|
||||
const PayZenModalWrapper: React.FC<PayZenModalProps> = ({ isOpen, toggleModal, afterSuccess, currentUser, schedule , cartItems, customer }) => {
|
||||
return (
|
||||
<Loader>
|
||||
<PayZenModal isOpen={isOpen} toggleModal={toggleModal} afterSuccess={afterSuccess} currentUser={currentUser} schedule={schedule} cartItems={cartItems} customer={customer} />
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
|
||||
Application.Components.component('payZenModal', react2angular(PayZenModalWrapper, ['isOpen', 'toggleModal', 'afterSuccess','currentUser', 'schedule', 'cartItems', 'customer']));
|
@ -14,14 +14,15 @@ interface StripeFormProps {
|
||||
operator: User,
|
||||
className?: string,
|
||||
paymentSchedule?: boolean,
|
||||
cartItems?: CartItems
|
||||
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="stripe-form".
|
||||
* The form validation button must be created elsewhere, using the attribute form={formId}.
|
||||
*/
|
||||
export const StripeForm: React.FC<StripeFormProps> = ({ onSubmit, onSuccess, onError, children, className, paymentSchedule = false, cartItems, customer, operator }) => {
|
||||
export const StripeForm: React.FC<StripeFormProps> = ({ onSubmit, onSuccess, onError, children, className, paymentSchedule = false, cartItems, customer, operator, formId }) => {
|
||||
|
||||
const { t } = useTranslation('shared');
|
||||
|
||||
@ -138,7 +139,7 @@ export const StripeForm: React.FC<StripeFormProps> = ({ onSubmit, onSuccess, onE
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} id="stripe-form" className={className}>
|
||||
<form onSubmit={handleSubmit} id={formId} className={className}>
|
||||
<CardElement options={cardOptions} />
|
||||
{children}
|
||||
</form>
|
||||
|
@ -1,30 +1,20 @@
|
||||
import React, { ReactNode, useEffect, useState } from 'react';
|
||||
import React, { FunctionComponent, ReactNode } from 'react';
|
||||
import { react2angular } from 'react2angular';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SetupIntent } from '@stripe/stripe-js';
|
||||
import WalletLib from '../../lib/wallet';
|
||||
import { StripeElements } from './stripe-elements';
|
||||
import { StripeForm } from './stripe-form';
|
||||
import { WalletInfo } from '../wallet-info';
|
||||
import { FabModal, ModalSize } from '../base/fab-modal';
|
||||
import { HtmlTranslate } from '../base/html-translate';
|
||||
import { Loader } from '../base/loader';
|
||||
import { IApplication } from '../../models/application';
|
||||
import { CustomAssetName } from '../../models/custom-asset';
|
||||
import { IFablab } from '../../models/fablab';
|
||||
import { CartItems, PaymentConfirmation } from '../../models/payment';
|
||||
import { PaymentSchedule } from '../../models/payment-schedule';
|
||||
import { User } from '../../models/user';
|
||||
import CustomAssetAPI from '../../api/custom-asset';
|
||||
import PriceAPI from '../../api/price';
|
||||
import WalletAPI from '../../api/wallet';
|
||||
|
||||
import stripeLogo from '../../../../images/powered_by_stripe.png';
|
||||
import mastercardLogo from '../../../../images/mastercard.png';
|
||||
import visaLogo from '../../../../images/visa.png';
|
||||
import { GatewayFormProps, PaymentModal } from '../base/payment-modal';
|
||||
|
||||
declare var Application: IApplication;
|
||||
declare var Fablab: IFablab;
|
||||
|
||||
interface StripeModalProps {
|
||||
isOpen: boolean,
|
||||
@ -36,87 +26,11 @@ interface StripeModalProps {
|
||||
customer: User
|
||||
}
|
||||
|
||||
// initial request to the API
|
||||
const cgvFile = CustomAssetAPI.get(CustomAssetName.CgvFile);
|
||||
|
||||
/**
|
||||
* This component enables the user to input his card data or process payments.
|
||||
* This component enables the user to input his card data or process payments, using the Stripe gateway.
|
||||
* Supports Strong-Customer Authentication (SCA).
|
||||
*/
|
||||
const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuccess, cartItems, currentUser, schedule, customer }) => {
|
||||
// customer's wallet
|
||||
const [wallet, setWallet] = useState(null);
|
||||
// server-computed price with all details
|
||||
const [price, setPrice] = useState(null);
|
||||
// remaining price = total price - wallet amount
|
||||
const [remainingPrice, setRemainingPrice] = useState(0);
|
||||
// is the component ready to display?
|
||||
const [ready, setReady] = useState(false);
|
||||
// errors to display in the UI (stripe errors mainly)
|
||||
const [errors, setErrors] = useState(null);
|
||||
// are we currently processing the payment (ie. the form was submit, but the process is still running)?
|
||||
const [submitState, setSubmitState] = useState(false);
|
||||
// did the user accepts the terms of services (CGV)?
|
||||
const [tos, setTos] = useState(false);
|
||||
|
||||
const { t } = useTranslation('shared');
|
||||
const cgv = cgvFile.read();
|
||||
|
||||
|
||||
/**
|
||||
* On each display:
|
||||
* - Refresh the wallet
|
||||
* - Refresh the price
|
||||
* - Refresh the remaining price
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!cartItems) return;
|
||||
WalletAPI.getByUser(cartItems.reservation?.user_id || cartItems.subscription?.user_id).then((wallet) => {
|
||||
setWallet(wallet);
|
||||
PriceAPI.compute(cartItems).then((res) => {
|
||||
setPrice(res);
|
||||
const wLib = new WalletLib(wallet);
|
||||
setRemainingPrice(wLib.computeRemainingPrice(res.price));
|
||||
setReady(true);
|
||||
})
|
||||
})
|
||||
}, [cartItems]);
|
||||
|
||||
/**
|
||||
* Check if there is currently an error to display
|
||||
*/
|
||||
const hasErrors = (): boolean => {
|
||||
return errors !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user accepts the Terms of Sales document
|
||||
*/
|
||||
const hasCgv = (): boolean => {
|
||||
return cgv != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when the user accepts or declines the Terms of Sales
|
||||
*/
|
||||
const toggleTos = (): void => {
|
||||
setTos(!tos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we are currently creating a payment schedule
|
||||
*/
|
||||
const isPaymentSchedule = (): boolean => {
|
||||
return schedule !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the formatted localized amount for the given price (eg. 20.5 => "20,50 €")
|
||||
*/
|
||||
const formatPrice = (amount: number): string => {
|
||||
return new Intl.NumberFormat(Fablab.intl_locale, {style: 'currency', currency: Fablab.intl_currency}).format(amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the logos, shown in the modal footer.
|
||||
*/
|
||||
@ -132,85 +46,39 @@ const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuc
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the component as 'currently submitting'
|
||||
* Integrates the StripeForm into the parent PaymentModal
|
||||
*/
|
||||
const handleSubmit = (): void => {
|
||||
setSubmitState(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* After sending the form with success, process the resulting payment method
|
||||
*/
|
||||
const handleFormSuccess = async (result: SetupIntent|PaymentConfirmation|any): Promise<void> => {
|
||||
setSubmitState(false);
|
||||
afterSuccess(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* When stripe-form raise an error, it is handled by this callback which display it in the modal.
|
||||
*/
|
||||
const handleFormError = (message: string): void => {
|
||||
setSubmitState(false);
|
||||
setErrors(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the form can be submitted.
|
||||
* => We're not currently already submitting the form, and, if the terms of service are enabled, the user agrees with them.
|
||||
*/
|
||||
const canSubmit = (): boolean => {
|
||||
let terms = true;
|
||||
if (hasCgv()) { terms = tos; }
|
||||
return !submitState && terms;
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<FabModal title={t('app.shared.stripe.online_payment')}
|
||||
isOpen={isOpen}
|
||||
toggleModal={toggleModal}
|
||||
width={ModalSize.medium}
|
||||
closeButton={false}
|
||||
customFooter={logoFooter()}
|
||||
className="stripe-modal">
|
||||
{ready && <StripeElements>
|
||||
<WalletInfo cartItems={cartItems} currentUser={currentUser} wallet={wallet} price={price?.price} />
|
||||
<StripeForm onSubmit={handleSubmit}
|
||||
onSuccess={handleFormSuccess}
|
||||
onError={handleFormError}
|
||||
operator={currentUser}
|
||||
className="stripe-form"
|
||||
const renderForm: FunctionComponent<GatewayFormProps> = ({ onSubmit, onSuccess, onError, operator, className, formId, cartItems, customer, paymentSchedule, children}) => {
|
||||
return (
|
||||
<StripeElements>
|
||||
<StripeForm onSubmit={onSubmit}
|
||||
onSuccess={onSuccess}
|
||||
onError={onError}
|
||||
operator={operator}
|
||||
className={className}
|
||||
formId={formId}
|
||||
cartItems={cartItems}
|
||||
customer={customer}
|
||||
paymentSchedule={isPaymentSchedule()}>
|
||||
{hasErrors() && <div className="stripe-errors">
|
||||
{errors}
|
||||
</div>}
|
||||
{isPaymentSchedule() && <div className="payment-schedule-info">
|
||||
<HtmlTranslate trKey="app.shared.stripe.payment_schedule_html" options={{ DEADLINES: schedule.items.length }} />
|
||||
</div>}
|
||||
{hasCgv() && <div className="terms-of-sales">
|
||||
<input type="checkbox" id="acceptToS" name="acceptCondition" checked={tos} onChange={toggleTos} required />
|
||||
<label htmlFor="acceptToS">{ t('app.shared.stripe.i_have_read_and_accept_') }
|
||||
<a href={cgv.custom_asset_file_attributes.attachment_url} target="_blank">
|
||||
{ t('app.shared.stripe._the_general_terms_and_conditions') }
|
||||
</a>
|
||||
</label>
|
||||
</div>}
|
||||
paymentSchedule={paymentSchedule}>
|
||||
{children}
|
||||
</StripeForm>
|
||||
{!submitState && <button type="submit"
|
||||
disabled={!canSubmit()}
|
||||
form="stripe-form"
|
||||
className="validate-btn">
|
||||
{t('app.shared.stripe.confirm_payment_of_', { AMOUNT: formatPrice(remainingPrice) })}
|
||||
</button>}
|
||||
{submitState && <div className="payment-pending">
|
||||
<div className="fa-2x">
|
||||
<i className="fas fa-circle-notch fa-spin" />
|
||||
</div>
|
||||
</div>}
|
||||
</StripeElements>}
|
||||
</FabModal>
|
||||
</StripeElements>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PaymentModal className="stripe-modal"
|
||||
isOpen={isOpen}
|
||||
toggleModal={toggleModal}
|
||||
logoFooter={logoFooter()}
|
||||
formId="stripe-form"
|
||||
formClassName="stripe-form"
|
||||
currentUser={currentUser}
|
||||
cartItems={cartItems}
|
||||
customer={customer}
|
||||
afterSuccess={afterSuccess}
|
||||
schedule={schedule}
|
||||
GatewayForm={renderForm} />
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -38,5 +38,6 @@
|
||||
@import "modules/stripe-keys-form";
|
||||
@import "modules/payzen-keys-form";
|
||||
@import "modules/payzen-settings";
|
||||
@import "modules/payment-modal";
|
||||
|
||||
@import "app.responsive";
|
||||
|
79
app/frontend/src/stylesheets/modules/payment-modal.scss
Normal file
79
app/frontend/src/stylesheets/modules/payment-modal.scss
Normal file
@ -0,0 +1,79 @@
|
||||
.payment-modal {
|
||||
.fab-modal-content {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.gateway-form {
|
||||
background-color: #f4f3f3;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
|
||||
padding: 15px;
|
||||
|
||||
.payment-errors {
|
||||
padding: 4px 0;
|
||||
color: #9e2146;
|
||||
overflow: auto;
|
||||
margin-bottom: 1.2em;
|
||||
}
|
||||
}
|
||||
.terms-of-sales {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 600;
|
||||
input {
|
||||
display: inline;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
label {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
.stripe-modal-icons {
|
||||
text-align: center;
|
||||
|
||||
.fa.fa-lock {
|
||||
top: 7px;
|
||||
color: #9edd78;
|
||||
}
|
||||
|
||||
img {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.payment-schedule-info {
|
||||
border: 1px solid #faebcc;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
background-color: #fcf8e3;
|
||||
color: #8a6d3b;
|
||||
margin-top: 1em;
|
||||
|
||||
p {
|
||||
font-size: small;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
}
|
||||
.validate-btn {
|
||||
width: 100%;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 0 0 6px 6px;
|
||||
border-top: 0;
|
||||
padding: 16px;
|
||||
color: #fff;
|
||||
background-color: #1d98ec;
|
||||
margin-bottom: 15px;
|
||||
|
||||
&[disabled] {
|
||||
background-color: lighten(#1d98ec, 20%);
|
||||
}
|
||||
}
|
||||
|
||||
.payment-pending {
|
||||
@extend .validate-btn;
|
||||
@extend .validate-btn[disabled];
|
||||
text-align: center;
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
@ -1,34 +1,4 @@
|
||||
.stripe-modal {
|
||||
.fab-modal-content {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.stripe-form {
|
||||
background-color: #f4f3f3;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
|
||||
padding: 15px;
|
||||
|
||||
.stripe-errors {
|
||||
padding: 4px 0;
|
||||
color: #9e2146;
|
||||
overflow: auto;
|
||||
margin-bottom: 1.2em;
|
||||
}
|
||||
}
|
||||
.terms-of-sales {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 600;
|
||||
input {
|
||||
display: inline;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
label {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
.stripe-modal-icons {
|
||||
text-align: center;
|
||||
|
||||
@ -41,39 +11,4 @@
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.payment-schedule-info {
|
||||
border: 1px solid #faebcc;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
background-color: #fcf8e3;
|
||||
color: #8a6d3b;
|
||||
margin-top: 1em;
|
||||
|
||||
p {
|
||||
font-size: small;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
}
|
||||
.validate-btn {
|
||||
width: 100%;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 0 0 6px 6px;
|
||||
border-top: 0;
|
||||
padding: 16px;
|
||||
color: #fff;
|
||||
background-color: #1d98ec;
|
||||
margin-bottom: 15px;
|
||||
|
||||
&[disabled] {
|
||||
background-color: lighten(#1d98ec, 20%);
|
||||
}
|
||||
}
|
||||
|
||||
.payment-pending {
|
||||
@extend .validate-btn;
|
||||
@extend .validate-btn[disabled];
|
||||
text-align: center;
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
|
@ -114,13 +114,11 @@ de:
|
||||
member_select:
|
||||
select_a_member: "Ein Mitglied auswählen"
|
||||
start_typing: "Tippe ein..."
|
||||
#stripe payment modal
|
||||
stripe:
|
||||
#payment modal
|
||||
payment:
|
||||
online_payment: "Online-Bezahlung"
|
||||
i_have_read_and_accept_: "Ich habe gelesen und akzeptiere "
|
||||
_the_general_terms_and_conditions: "die allgemeinen Nutzungs- und Geschäftsbedingungen."
|
||||
credit_amount_for_pay_reservation: "{amount} {currency} muss noch bezahlt werden, um Ihre Reservierung zu bestätigen"
|
||||
client_credit_amount_for_pay_reservation: "{amount} {currency} muss noch bezahlt werden, um die Reservierung des Kunden zu bestätigen"
|
||||
payment_schedule_html: "<p>You're about to subscribe to a payment schedule of {DEADLINES} months.</p><p>By paying this bill, you agree to send instructions to the financial institution that issue your card, to take payments from your card account, for the whole duration of this subscription. This imply that your card data are saved by Stripe and a series of payments will be initiated on your behalf, conforming to the payment schedule previously shown.</p>"
|
||||
confirm_payment_of_: "Bezahlen: {AMOUNT}"
|
||||
#dialog of on site payment for reservations
|
||||
|
@ -114,13 +114,11 @@ en:
|
||||
member_select:
|
||||
select_a_member: "Select a member"
|
||||
start_typing: "Start typing..."
|
||||
#stripe payment modal
|
||||
stripe:
|
||||
#payment modal
|
||||
payment:
|
||||
online_payment: "Online payment"
|
||||
i_have_read_and_accept_: "I have read, and accept "
|
||||
_the_general_terms_and_conditions: "the general terms and conditions."
|
||||
credit_amount_for_pay_reservation: "{amount} {currency} remains to be paid to confirm your reservation"
|
||||
client_credit_amount_for_pay_reservation: "{amount} {currency} remains to be paid to confirm reservation of client"
|
||||
payment_schedule_html: "<p>You're about to subscribe to a payment schedule of {DEADLINES} months.</p><p>By paying this bill, you agree to send instructions to the financial institution that issue your card, to take payments from your card account, for the whole duration of this subscription. This imply that your card data are saved by Stripe and a series of payments will be initiated on your behalf, conforming to the payment schedule previously shown.</p>"
|
||||
confirm_payment_of_: "Pay: {AMOUNT}"
|
||||
#dialog of on site payment for reservations
|
||||
|
@ -114,13 +114,11 @@ es:
|
||||
member_select:
|
||||
select_a_member: "Selecciona un miembro"
|
||||
start_typing: "Empezar a escribir..."
|
||||
#stripe payment modal
|
||||
stripe:
|
||||
#payment modal
|
||||
payment:
|
||||
online_payment: "Online payment"
|
||||
i_have_read_and_accept_: "He leido y acepto "
|
||||
_the_general_terms_and_conditions: "Los términos y condiciones."
|
||||
credit_amount_for_pay_reservation: "{amount} {currency} falta por pagar para efectuar su reserva"
|
||||
client_credit_amount_for_pay_reservation: "{amount} {currency} falta por pagar para efectuar la reserva del cliente"
|
||||
payment_schedule_html: "<p>You're about to subscribe to a payment schedule of {DEADLINES} months.</p><p>By paying this bill, you agree to send instructions to the financial institution that issue your card, to take payments from your card account, for the whole duration of this subscription. This imply that your card data are saved by Stripe and a series of payments will be initiated on your behalf, conforming to the payment schedule previously shown.</p>"
|
||||
confirm_payment_of_: "Pay: {AMOUNT}"
|
||||
#dialog of on site payment for reservations
|
||||
|
@ -114,13 +114,11 @@ fr:
|
||||
member_select:
|
||||
select_a_member: "Sélectionnez un membre"
|
||||
start_typing: "Commencez à écrire..."
|
||||
#stripe payment modal
|
||||
stripe:
|
||||
#payment modal
|
||||
payment:
|
||||
online_payment: "Paiement en ligne"
|
||||
i_have_read_and_accept_: "J'ai bien pris connaissance, et accepte "
|
||||
_the_general_terms_and_conditions: "les conditions générales de vente."
|
||||
credit_amount_for_pay_reservation: "Il vous reste {amount} {currency} à payer pour valider votre réservation"
|
||||
client_credit_amount_for_pay_reservation: "Il reste {amount} {currency} à payer pour valider la réservation"
|
||||
payment_schedule_html: "<p>Vous êtes sur le point de souscrire à un échéancier de paiement de {DEADLINES} mois.</p><p>En payant cette facture, vous vous engagez à l'envoi d'instructions vers l'institution financière émettrice de votre carte, afin de prélever des paiements sur votre compte, pendant toute la durée de cet abonnement. Cela implique que les données de votre carte soient enregistrées par Stripe et qu'une série de paiements sera initiée en votre nom, conformément à l'échéancier de paiement précédemment affiché.</p>"
|
||||
confirm_payment_of_: "Payer : {AMOUNT}"
|
||||
#dialog of on site payment for reservations
|
||||
|
@ -114,13 +114,11 @@ pt:
|
||||
member_select:
|
||||
select_a_member: "Selecionar um membro"
|
||||
start_typing: "Escrevendo..."
|
||||
#stripe payment modal
|
||||
stripe:
|
||||
#payment modal
|
||||
payment:
|
||||
online_payment: "Pagamento Online"
|
||||
i_have_read_and_accept_: "Eu li e aceito "
|
||||
_the_general_terms_and_conditions: "os termos e condições."
|
||||
credit_amount_for_pay_reservation: "{amount} {currency} a ser pago para confirmar sua inscrição"
|
||||
client_credit_amount_for_pay_reservation: "{amount} {currency} a ser pago para confirmar a inscrição do cliente"
|
||||
payment_schedule_html: "<p>You're about to subscribe to a payment schedule of {DEADLINES} months.</p><p>By paying this bill, you agree to send instructions to the financial institution that issue your card, to take payments from your card account, for the whole duration of this subscription. This imply that your card data are saved by Stripe and a series of payments will be initiated on your behalf, conforming to the payment schedule previously shown.</p>"
|
||||
confirm_payment_of_: "Pagamento: {AMOUNT}"
|
||||
#dialog of on site payment for reservations
|
||||
|
@ -114,13 +114,11 @@ zu:
|
||||
member_select:
|
||||
select_a_member: "crwdns9571:0crwdne9571:0"
|
||||
start_typing: "crwdns9573:0crwdne9573:0"
|
||||
#stripe payment modal
|
||||
stripe:
|
||||
#payment modal
|
||||
payment:
|
||||
online_payment: "crwdns9575:0crwdne9575:0"
|
||||
i_have_read_and_accept_: "crwdns20916:0crwdne20916:0"
|
||||
_the_general_terms_and_conditions: "crwdns9579:0crwdne9579:0"
|
||||
credit_amount_for_pay_reservation: "crwdns9581:0{amount}crwdnd9581:0{currency}crwdne9581:0"
|
||||
client_credit_amount_for_pay_reservation: "crwdns9583:0{amount}crwdnd9583:0{currency}crwdne9583:0"
|
||||
payment_schedule_html: "crwdns20918:0{DEADLINES}crwdne20918:0"
|
||||
confirm_payment_of_: "crwdns9585:0{AMOUNT}crwdne9585:0"
|
||||
#dialog of on site payment for reservations
|
||||
|
Loading…
x
Reference in New Issue
Block a user