diff --git a/app/frontend/images/payzen-secure.png b/app/frontend/images/payzen-secure.png new file mode 100644 index 000000000..438d724ce Binary files /dev/null and b/app/frontend/images/payzen-secure.png differ diff --git a/app/frontend/src/javascript/components/base/payment-modal.tsx b/app/frontend/src/javascript/components/base/payment-modal.tsx new file mode 100644 index 000000000..2508f79ea --- /dev/null +++ b/app/frontend/src/javascript/components/base/payment-modal.tsx @@ -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, + 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 = ({ 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 => { + 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 ( + + {ready &&
+ + + {hasErrors() &&
+ {errors} +
} + {isPaymentSchedule() &&
+ +
} + {hasCgv() &&
+ + +
} +
+ {!submitState && } + {submitState &&
+
+ +
+
} +
} +
+ ); +} diff --git a/app/frontend/src/javascript/components/payzen/payzen-modal.tsx b/app/frontend/src/javascript/components/payzen/payzen-modal.tsx new file mode 100644 index 000000000..389b7400d --- /dev/null +++ b/app/frontend/src/javascript/components/payzen/payzen-modal.tsx @@ -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 = ({ isOpen, toggleModal, afterSuccess, cartItems, currentUser, schedule, customer }) => { + /** + * Return the logos, shown in the modal footer. + */ + const logoFooter = (): ReactNode => { + return ( +
+ + powered by PayZen + mastercard + visa +
+ ); + } + + /** + * Integrates the PayzenForm into the parent PaymentModal + */ + const renderForm: FunctionComponent = ({ onSubmit, onSuccess, onError, operator, className, formId, cartItems, customer, paymentSchedule, children}) => { + return ( +
+

PayZen

+ + Operated by {operator.name} + User: {customer.name} + {children} +
+ ); + } + + return ( + + ); +} + +const PayZenModalWrapper: React.FC = ({ isOpen, toggleModal, afterSuccess, currentUser, schedule , cartItems, customer }) => { + return ( + + + + ); +} + +Application.Components.component('payZenModal', react2angular(PayZenModalWrapper, ['isOpen', 'toggleModal', 'afterSuccess','currentUser', 'schedule', 'cartItems', 'customer'])); diff --git a/app/frontend/src/javascript/components/stripe/stripe-form.tsx b/app/frontend/src/javascript/components/stripe/stripe-form.tsx index 56130f59e..fb7967aed 100644 --- a/app/frontend/src/javascript/components/stripe/stripe-form.tsx +++ b/app/frontend/src/javascript/components/stripe/stripe-form.tsx @@ -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 = ({ onSubmit, onSuccess, onError, children, className, paymentSchedule = false, cartItems, customer, operator }) => { +export const StripeForm: React.FC = ({ onSubmit, onSuccess, onError, children, className, paymentSchedule = false, cartItems, customer, operator, formId }) => { const { t } = useTranslation('shared'); @@ -138,7 +139,7 @@ export const StripeForm: React.FC = ({ onSubmit, onSuccess, onE }; return ( -
+ {children} diff --git a/app/frontend/src/javascript/components/stripe/stripe-modal.tsx b/app/frontend/src/javascript/components/stripe/stripe-modal.tsx index 97378e2be..a6692fa46 100644 --- a/app/frontend/src/javascript/components/stripe/stripe-modal.tsx +++ b/app/frontend/src/javascript/components/stripe/stripe-modal.tsx @@ -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 = ({ 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 = ({ 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 => { - 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 ( - - {ready && - - = ({ onSubmit, onSuccess, onError, operator, className, formId, cartItems, customer, paymentSchedule, children}) => { + return ( + + - {hasErrors() &&
- {errors} -
} - {isPaymentSchedule() &&
- -
} - {hasCgv() &&
- - -
} + paymentSchedule={paymentSchedule}> + {children}
- {!submitState && } - {submitState &&
-
- -
-
} -
} -
+ + ); + } + + return ( + ); } diff --git a/app/frontend/src/stylesheets/application.scss b/app/frontend/src/stylesheets/application.scss index 5c2aebbe4..50453f44c 100644 --- a/app/frontend/src/stylesheets/application.scss +++ b/app/frontend/src/stylesheets/application.scss @@ -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"; diff --git a/app/frontend/src/stylesheets/modules/payment-modal.scss b/app/frontend/src/stylesheets/modules/payment-modal.scss new file mode 100644 index 000000000..1f36aa651 --- /dev/null +++ b/app/frontend/src/stylesheets/modules/payment-modal.scss @@ -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; + } +} diff --git a/app/frontend/src/stylesheets/modules/stripe-modal.scss b/app/frontend/src/stylesheets/modules/stripe-modal.scss index 6e1a370c6..1340cf746 100644 --- a/app/frontend/src/stylesheets/modules/stripe-modal.scss +++ b/app/frontend/src/stylesheets/modules/stripe-modal.scss @@ -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; - } } diff --git a/config/locales/app.shared.de.yml b/config/locales/app.shared.de.yml index b965df5db..d58bf6e7f 100644 --- a/config/locales/app.shared.de.yml +++ b/config/locales/app.shared.de.yml @@ -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: "

You're about to subscribe to a payment schedule of {DEADLINES} months.

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.

" confirm_payment_of_: "Bezahlen: {AMOUNT}" #dialog of on site payment for reservations diff --git a/config/locales/app.shared.en.yml b/config/locales/app.shared.en.yml index 4ebb8e270..2dd75ffef 100644 --- a/config/locales/app.shared.en.yml +++ b/config/locales/app.shared.en.yml @@ -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: "

You're about to subscribe to a payment schedule of {DEADLINES} months.

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.

" confirm_payment_of_: "Pay: {AMOUNT}" #dialog of on site payment for reservations diff --git a/config/locales/app.shared.es.yml b/config/locales/app.shared.es.yml index c4c13e744..35d8fbca3 100644 --- a/config/locales/app.shared.es.yml +++ b/config/locales/app.shared.es.yml @@ -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: "

You're about to subscribe to a payment schedule of {DEADLINES} months.

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.

" confirm_payment_of_: "Pay: {AMOUNT}" #dialog of on site payment for reservations diff --git a/config/locales/app.shared.fr.yml b/config/locales/app.shared.fr.yml index a81baae2e..24815c5cb 100644 --- a/config/locales/app.shared.fr.yml +++ b/config/locales/app.shared.fr.yml @@ -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: "

Vous êtes sur le point de souscrire à un échéancier de paiement de {DEADLINES} mois.

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é.

" confirm_payment_of_: "Payer : {AMOUNT}" #dialog of on site payment for reservations diff --git a/config/locales/app.shared.pt.yml b/config/locales/app.shared.pt.yml index d630ab27e..89f0f363f 100755 --- a/config/locales/app.shared.pt.yml +++ b/config/locales/app.shared.pt.yml @@ -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: "

You're about to subscribe to a payment schedule of {DEADLINES} months.

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.

" confirm_payment_of_: "Pagamento: {AMOUNT}" #dialog of on site payment for reservations diff --git a/config/locales/app.shared.zu.yml b/config/locales/app.shared.zu.yml index 88d40b6e9..8ec80f240 100644 --- a/config/locales/app.shared.zu.yml +++ b/config/locales/app.shared.zu.yml @@ -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