1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-02-19 13:54:25 +01:00

use localPaymentModal in cart directive

This commit is contained in:
Sylvain 2021-06-30 16:35:25 +02:00
parent d43f719038
commit 19717d1351
8 changed files with 96 additions and 196 deletions

View File

@ -30,7 +30,7 @@ class API::StripeController < API::PaymentsController
currency: Setting.get('stripe_currency'),
confirmation_method: 'manual',
confirm: true,
customer: current_user.payment_gateway_object.gateway_object_id
customer: cart.customer.payment_gateway_object.gateway_object_id
}, { api_key: Setting.get('stripe_secret_key') }
)
elsif params[:payment_intent_id].present?

View File

@ -46,6 +46,7 @@ interface AbstractPaymentModalProps {
formClassName?: string,
title?: string,
preventCgv?: boolean,
preventScheduleInfo?: boolean,
modalSize?: ModalSize,
}
@ -55,7 +56,7 @@ interface AbstractPaymentModalProps {
* 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, cart, currentUser, schedule, customer, logoFooter, GatewayForm, formId, className, formClassName, title, preventCgv, modalSize }) => {
export const AbstractPaymentModal: React.FC<AbstractPaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, cart, currentUser, schedule, customer, logoFooter, GatewayForm, formId, className, formClassName, title, preventCgv, preventScheduleInfo, modalSize }) => {
// customer's wallet
const [wallet, setWallet] = useState<Wallet>(null);
// server-computed price with all details
@ -129,10 +130,10 @@ export const AbstractPaymentModal: React.FC<AbstractPaymentModalProps> = ({ isOp
}
/**
* Check if we are currently creating a payment schedule
* Check if we must display the info box about the payment schedule
*/
const isPaymentSchedule = (): boolean => {
return schedule !== undefined;
const hasPaymentScheduleInfo = (): boolean => {
return schedule !== undefined && !preventScheduleInfo;
}
/**
@ -202,7 +203,7 @@ export const AbstractPaymentModal: React.FC<AbstractPaymentModalProps> = ({ isOp
{hasErrors() && <div className="payment-errors">
{errors}
</div>}
{isPaymentSchedule() && <div className="payment-schedule-info">
{hasPaymentScheduleInfo() && <div className="payment-schedule-info">
<HtmlTranslate trKey="app.shared.payment.payment_schedule_html" options={{ DEADLINES: schedule.items.length, GATEWAY: gateway }} />
</div>}
{hasCgv() && <div className="terms-of-sales">
@ -233,5 +234,6 @@ export const AbstractPaymentModal: React.FC<AbstractPaymentModalProps> = ({ isOp
AbstractPaymentModal.defaultProps = {
title: 'app.shared.payment.online_payment',
preventCgv: false,
preventScheduleInfo: false,
modalSize: ModalSize.medium
};

View File

@ -103,10 +103,12 @@ export const LocalPaymentForm: React.FC<GatewayFormProps> = ({ onSubmit, onSucce
{!paymentSchedule && <p className="payment">{t('app.admin.local_payment.about_to_cash')}</p>}
{paymentSchedule && <div className="payment-schedule">
<div className="schedule-method">
<label htmlFor="payment-method">{t('app.admin.local_payment.payment_method')}</label>
<Select placeholder={ t('app.admin.local_payment.payment_method') }
id="payment-method"
className="method-select"
onChange={handleUpdateMethod}
options={buildMethodOptions}
options={buildMethodOptions()}
defaultValue={methodToOption(method)} />
{method === 'card' && <p>{t('app.admin.local_payment.card_collection_info')}</p>}
{method === 'check' && <p>{t('app.admin.local_payment.check_collection_info', { DEADLINES: paymentSchedule.items.length })}</p>}
@ -115,7 +117,7 @@ export const LocalPaymentForm: React.FC<GatewayFormProps> = ({ onSubmit, onSucce
<ul>
{paymentSchedule.items.map(item => {
return (
<li key={item.id}>
<li key={`${item.due_date}`}>
<span className="schedule-item-date">{FormatLib.date(item.due_date)}</span>
<span> </span>
<span className="schedule-item-price">{FormatLib.price(item.amount)}</span>

View File

@ -7,7 +7,11 @@ import { User } from '../../../models/user';
import { Invoice } from '../../../models/invoice';
import { useTranslation } from 'react-i18next';
import { ModalSize } from '../../base/fab-modal';
import { Loader } from '../../base/loader';
import { react2angular } from 'react2angular';
import { IApplication } from '../../../models/application';
declare var Application: IApplication;
interface LocalPaymentModalProps {
isOpen: boolean,
@ -22,7 +26,7 @@ interface LocalPaymentModalProps {
/**
* This component enables a privileged user to confirm a local payments.
*/
export const LocalPaymentModal: React.FC<LocalPaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, cart, currentUser, schedule, customer }) => {
const LocalPaymentModalComponent: React.FC<LocalPaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, cart, currentUser, schedule, customer }) => {
const { t } = useTranslation('admin');
@ -71,6 +75,17 @@ export const LocalPaymentModal: React.FC<LocalPaymentModalProps> = ({ isOpen, to
schedule={schedule}
GatewayForm={renderForm}
modalSize={schedule ? ModalSize.large : ModalSize.medium}
preventCgv />
preventCgv
preventScheduleInfo />
);
}
export const LocalPaymentModal: React.FC<LocalPaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, currentUser, schedule , cart, customer }) => {
return (
<Loader>
<LocalPaymentModalComponent isOpen={isOpen} toggleModal={toggleModal} afterSuccess={afterSuccess} currentUser={currentUser} schedule={schedule} cart={cart} customer={customer} />
</Loader>
);
}
Application.Components.component('localPaymentModal', react2angular(LocalPaymentModal, ['isOpen', 'toggleModal', 'afterSuccess', 'currentUser', 'schedule', 'cart', 'customer']));

View File

@ -79,6 +79,12 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
cartItems: undefined
};
// offline payments (at the fablab's reception)
$scope.localPayment = {
showModal: false,
cartItems: undefined
};
// currently logged-in user
$scope.currentUser = $rootScope.currentUser;
@ -325,6 +331,19 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
}, 50);
};
/**
* This will open/close the local payment modal
*/
$scope.toggleLocalPaymentModal = (beforeApply) => {
setTimeout(() => {
$scope.localPayment.showModal = !$scope.localPayment.showModal;
if (typeof beforeApply === 'function') {
beforeApply();
}
$scope.$apply();
}, 50);
};
/**
* Invoked atfer a successful card payment
* @param invoice {*} may be an Invoice or a paymentSchedule
@ -334,6 +353,15 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
afterPayment(invoice);
};
/**
* Invoked atfer a successful offline payment
* @param invoice {*} may be an Invoice or a paymentSchedule
*/
$scope.afterLocalPaymentSuccess = (invoice) => {
$scope.toggleLocalPaymentModal();
afterPayment(invoice);
};
/**
* Invoked when something wrong occurred during the payment dialog initialization
* @param message {string}
@ -717,195 +745,14 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
});
}
};
/**
* Open a modal window that allows the user to process a local payment for his current shopping cart (admin only).
*/
const payOnSite = function (items) {
$uibModal.open({
templateUrl: '/shared/valid_reservation_modal.html',
size: $scope.schedule.payment_schedule ? 'lg' : 'sm',
resolve: {
price () {
return Price.compute(mkCartItems(items, '')).$promise;
},
cartItems () {
return mkCartItems(items, '');
},
wallet () {
return Wallet.getWalletByUser({ user_id: $scope.user.id }).$promise;
},
coupon () {
return $scope.coupon.applied;
},
selectedPlan () {
return $scope.selectedPlan;
},
schedule () {
return $scope.schedule;
},
user () {
return $scope.user;
},
settings () {
return $scope.settings;
}
},
controller: ['$scope', '$uibModalInstance', '$state', 'price', 'Auth', 'LocalPayment', 'wallet', 'helpers', '$filter', 'coupon', 'selectedPlan', 'schedule', 'cartItems', 'user', 'settings',
function ($scope, $uibModalInstance, $state, price, Auth, LocalPayment, wallet, helpers, $filter, coupon, selectedPlan, schedule, cartItems, user, settings) {
// user wallet amount
$scope.wallet = wallet;
// Global price (total of all items)
$scope.price = price.price;
// Price to pay (wallet deducted)
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount);
// Reservation &| subscription
$scope.cartItems = cartItems;
// Subscription
$scope.plan = selectedPlan;
// Used in wallet info template to interpolate some translations
$scope.numberFilter = $filter('number');
// Shows the schedule info in the modal
$scope.schedule = schedule.payment_schedule;
// how should we collect payments for the payment schedule
$scope.method = {
payment_method: 'card'
};
// "valid" Button label
$scope.validButtonName = '';
// online payment modal state
// this is used to collect card data when a payment-schedule was selected, and paid with a card
$scope.isOpenOnlinePaymentModal = false;
// the customer
$scope.user = user;
/**
* Check if the shopping cart contains a reservation
* @return {Reservation|boolean}
*/
$scope.reservation = (function () {
const item = cartItems.items.find(i => i.reservation);
if (item && item.reservation.slots_attributes.length > 0) {
return item.reservation;
}
return false;
})();
/**
* Check if the shopping cart contains a subscription
* @return {Subscription|boolean}
*/
$scope.subscription = (function () {
const item = cartItems.items.find(i => i.subscription);
if (item && item.subscription.plan_id) {
return item.subscription;
}
return false;
})();
/**
* Callback to process the local payment, triggered on button click
*/
$scope.ok = function () {
if ($scope.schedule && $scope.method.payment_method === 'card') {
// check that the online payment is enabled
if (settings.online_payment_module !== 'true') {
return growl.error(_t('app.shared.cart.online_payment_disabled'));
} else {
return $scope.toggleOnlinePaymentModal();
}
}
$scope.attempting = true;
LocalPayment.confirm(cartItems, function (reservation) {
$uibModalInstance.close(reservation);
$scope.attempting = true;
}, function (response) {
$scope.alerts = [];
$scope.alerts.push({ msg: _t('app.shared.cart.a_problem_occurred_during_the_payment_process_please_try_again_later'), type: 'danger' });
$scope.attempting = false;
});
};
/**
* Callback to close the modal without processing the payment
*/
$scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
/**
* Asynchronously updates the status of the online payment modal
*/
$scope.toggleOnlinePaymentModal = function () {
setTimeout(() => {
$scope.isOpenOnlinePaymentModal = !$scope.isOpenOnlinePaymentModal;
$scope.$apply();
}, 50);
};
/**
* After creating a payment schedule by card, from an administrator.
* @param result {*} PaymentSchedule
*/
$scope.afterCreatePaymentSchedule = function (result) {
$scope.toggleOnlinePaymentModal();
$uibModalInstance.close(result);
};
/**
* Invoked when something wrong occurred during the payment dialog initialization
* @param message {string}
*/
$scope.onCreatePaymentScheduleError = (message) => {
growl.error(message);
};
/* PRIVATE SCOPE */
/**
* Kind of constructor: these actions will be realized first when the directive is loaded
*/
const initialize = function () {
$scope.$watch('method.payment_method', function (newValue) {
$scope.validButtonName = computeValidButtonName();
$scope.cartItems.payment_method = newValue;
});
};
/**
* Compute the Label of the confirmation button
*/
const computeValidButtonName = function () {
let method = '';
if ($scope.schedule) {
if (AuthService.isAuthorized(['admin', 'manager']) && $rootScope.currentUser.id !== cartItems.customer_id) {
method = $scope.method.payment_method;
} else {
method = 'card';
}
}
if ($scope.amount > 0) {
return _t('app.shared.cart.confirm_payment_of_html', { METHOD: method, AMOUNT: $filter('currency')($scope.amount) });
} else {
if ((price.price > 0) && ($scope.wallet.amount === 0)) {
return _t('app.shared.cart.confirm_payment_of_html', { METHOD: method, AMOUNT: $filter('currency')(price.price) });
} else {
return _t('app.shared.buttons.confirm');
}
}
};
// # !!! MUST BE CALLED AT THE END of the controller
initialize();
}
]
}).result.finally(null).then(function (paymentSchedule) { afterPayment(paymentSchedule); });
$scope.toggleLocalPaymentModal(() => {
$scope.localPayment.cartItems = mkCartItems(items);
});
};
/**

View File

@ -1,4 +1,28 @@
.local-payment-modal {
.local-payment-form {
.payment-schedule {
display: flex;
.schedule-method,
.full-schedule {
width: 50%;
}
.schedule-method {
p {
margin-top: 12px;
}
}
.full-schedule {
ul {
list-style: none;
}
}
}
}
.local-modal-icons {
text-align: center;

View File

@ -209,3 +209,13 @@
customer="user"
schedule="schedule.payment_schedule"/>
</div>
<div ng-if="localPayment.showModal">
<local-payment-modal is-open="localPayment.showModal"
toggle-modal="toggleLocalPaymentModal"
after-success="afterLocalPaymentSuccess"
cart="localPayment.cartItems"
current-user="currentUser"
customer="user"
schedule="schedule.payment_schedule"/>
</div>

View File

@ -1360,7 +1360,7 @@ en:
category_deleted: "The category was successfully deleted"
unable_to_delete: "Unable to delete the category: "
local_payment:
offline_payment: "Offline payment"
offline_payment: "Payment on site"
about_to_cash: "You're about to confirm the cashing by an external payment mean. Please do not click on the button below until you have fully cashed the requested payment."
payment_method: "Payment method"
method_card: "Online by card"