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

reserve events w/ payzen

we cannot use the <cart> directive because the layout is too much different
This commit is contained in:
Sylvain 2021-04-29 16:29:35 +02:00
parent ee1cdb417e
commit aaf36dcc0a
9 changed files with 86 additions and 177 deletions

View File

@ -48,9 +48,9 @@ export const PayzenForm: React.FC<GatewayFormProps> = ({ onSubmit, onSuccess, on
const transaction = event.clientAnswer.transactions[0]; const transaction = event.clientAnswer.transactions[0];
if (event.clientAnswer.orderStatus === 'PAID') { if (event.clientAnswer.orderStatus === 'PAID') {
PayzenAPI.confirm(event.clientAnswer.orderDetails.orderId, cartItems).then(() => { PayzenAPI.confirm(event.clientAnswer.orderDetails.orderId, cartItems).then((confirmation) => {
PayZenKR.current.removeForms().then(() => { PayZenKR.current.removeForms().then(() => {
onSuccess(event.clientAnswer); onSuccess(confirmation);
}); });
}) })
} else { } else {

View File

@ -284,7 +284,8 @@ Application.Controllers.controller('AdminEventsController', ['$scope', '$state',
resolve: { resolve: {
category () { return {}; } category () { return {}; }
}, },
controller: 'PriceCategoryController' }).result['finally'](null).then(function (p_cat) { controller: 'PriceCategoryController'
}).result.finally(null).then(function (p_cat) {
// save the price category to the API // save the price category to the API
PriceCategory.save(p_cat, function (cat) { PriceCategory.save(p_cat, function (cat) {
$scope.priceCategories.push(cat); $scope.priceCategories.push(cat);
@ -312,7 +313,8 @@ Application.Controllers.controller('AdminEventsController', ['$scope', '$state',
resolve: { resolve: {
category () { return $scope.priceCategories[index]; } category () { return $scope.priceCategories[index]; }
}, },
controller: 'PriceCategoryController' }).result['finally'](null).then(function (p_cat) { controller: 'PriceCategoryController'
}).result.finally(null).then(function (p_cat) {
// update the price category to the API // update the price category to the API
PriceCategory.update({ id }, { price_category: p_cat }, function (cat) { PriceCategory.update({ id }, { price_category: p_cat }, function (cat) {
$scope.priceCategories[index] = cat; $scope.priceCategories[index] = cat;
@ -374,7 +376,6 @@ Application.Controllers.controller('AdminEventsController', ['$scope', '$state',
return $scope.page = 1; return $scope.page = 1;
}; };
/** /**
* Setup the feature-tour for the admin/events page. * Setup the feature-tour for the admin/events page.
* This is intended as a contextual help (when pressing F1) * This is intended as a contextual help (when pressing F1)
@ -468,7 +469,7 @@ Application.Controllers.controller('AdminEventsController', ['$scope', '$state',
if (settingsPromise.feature_tour_display !== 'manual' && $scope.currentUser.profile.tours.indexOf('events') < 0) { if (settingsPromise.feature_tour_display !== 'manual' && $scope.currentUser.profile.tours.indexOf('events') < 0) {
uitour.start(); uitour.start();
} }
} };
/* PRIVATE SCOPE */ /* PRIVATE SCOPE */
@ -532,9 +533,9 @@ Application.Controllers.controller('ShowEventReservationsController', ['$scope',
* @param reservation {Reservation} * @param reservation {Reservation}
* @returns {boolean} * @returns {boolean}
*/ */
$scope.isCancelled = function(reservation) { $scope.isCancelled = function (reservation) {
return !!(reservation.slots[0].canceled_at); return !!(reservation.slots_attributes[0].canceled_at);
} };
}]); }]);
/** /**
@ -585,7 +586,7 @@ Application.Controllers.controller('NewEventController', ['$scope', '$state', 'C
]; ];
// triggered when the new event form was submitted to the API and have received an answer // triggered when the new event form was submitted to the API and have received an answer
$scope.onSubmited = function(content) { $scope.onSubmited = function (content) {
if ((content.id == null)) { if ((content.id == null)) {
$scope.alerts = []; $scope.alerts = [];
angular.forEach(content, function (v, k) { angular.forEach(content, function (v, k) {
@ -655,7 +656,7 @@ Application.Controllers.controller('EditEventController', ['$scope', '$state', '
} }
}); });
// submit form event by edit-mode // submit form event by edit-mode
modalInstance.result.then(function(res) { modalInstance.result.then(function (res) {
$scope.isShowEditModeModal = false; $scope.isShowEditModeModal = false;
$scope.editMode = res.editMode; $scope.editMode = res.editMode;
e.target.click(); e.target.click();
@ -664,12 +665,12 @@ Application.Controllers.controller('EditEventController', ['$scope', '$state', '
}; };
// triggered when the edit event form was submitted to the API and have received an answer // triggered when the edit event form was submitted to the API and have received an answer
$scope.onSubmited = function(data) { $scope.onSubmited = function (data) {
if (data.total === data.updated) { if (data.total === data.updated) {
if (data.updated > 1) { if (data.updated > 1) {
growl.success(_t( growl.success(_t(
'app.admin.events_edit.events_updated', 'app.admin.events_edit.events_updated',
{COUNT: data.updated - 1} { COUNT: data.updated - 1 }
)); ));
} else { } else {
growl.success(_t( growl.success(_t(
@ -680,7 +681,7 @@ Application.Controllers.controller('EditEventController', ['$scope', '$state', '
if (data.total > 1) { if (data.total > 1) {
growl.warning(_t( growl.warning(_t(
'app.admin.events_edit.events_not_updated', 'app.admin.events_edit.events_not_updated',
{TOTAL: data.total, COUNT: data.total - data.updated} { TOTAL: data.total, COUNT: data.total - data.updated }
)); ));
if (_.find(data.details, { error: 'EventPriceCategory' })) { if (_.find(data.details, { error: 'EventPriceCategory' })) {
growl.error(_t( growl.error(_t(
@ -750,20 +751,20 @@ Application.Controllers.controller('EditRecurrentEventController', ['$scope', '$
$uibModalInstance.close({ $uibModalInstance.close({
editMode: $scope.editMode editMode: $scope.editMode
}); });
} };
/** /**
* Test if any of the dates of the event has changed * Test if any of the dates of the event has changed
*/ */
$scope.hasDateChanged = function() { $scope.hasDateChanged = function () {
return (!moment(initialDates.start).isSame(currentEvent.start_date, 'day') || !moment(initialDates.end).isSame(currentEvent.end_date, 'day')); return (!moment(initialDates.start).isSame(currentEvent.start_date, 'day') || !moment(initialDates.end).isSame(currentEvent.end_date, 'day'));
} };
/** /**
* Cancellation callback * Cancellation callback
*/ */
$scope.cancel = function () { $scope.cancel = function () {
$uibModalInstance.dismiss('cancel'); $uibModalInstance.dismiss('cancel');
} };
} }
]); ]);

View File

@ -178,11 +178,11 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
// Message displayed to the end user about rules that applies to events reservations // Message displayed to the end user about rules that applies to events reservations
$scope.eventExplicationsAlert = settingsPromise.event_explications_alert; $scope.eventExplicationsAlert = settingsPromise.event_explications_alert;
// the application global settings, required in <cart> // online payments (by card)
$scope.settings = settingsPromise $scope.onlinePayment = {
showModal: false,
// the moment when the slot selection changed for the last time, used to trigger changes in the cart cartItems: undefined
$scope.selectionTime = null; };
/** /**
* Callback to delete the provided event (admins only) * Callback to delete the provided event (admins only)
@ -313,7 +313,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
if (settingsPromise.online_payment_module !== 'true') { if (settingsPromise.online_payment_module !== 'true') {
growl.error(_t('app.public.events_show.online_payment_disabled')); growl.error(_t('app.public.events_show.online_payment_disabled'));
} else { } else {
return payByStripe(reservation); return payOnline(reservation);
} }
} else { } else {
if (AuthService.isAuthorized('admin') if (AuthService.isAuthorized('admin')
@ -381,7 +381,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
/** /**
* Callback to cancel a reservation * Callback to cancel a reservation
* @param reservation {{id:number, reservable_id:number, nb_reserve_places:number}} * @param reservation {{id:number, reservable_id:number, nb_reserve_places:number, slots_attributes:[{id: number, canceled_at: string}], total_booked_seats: number}}
*/ */
$scope.cancelReservation = function(reservation) { $scope.cancelReservation = function(reservation) {
dialogs.confirm({ dialogs.confirm({
@ -395,13 +395,13 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
} }
}, function() { // cancel confirmed }, function() { // cancel confirmed
Slot.cancel({ Slot.cancel({
id: reservation.slots[0].id id: reservation.slots_attributes[0].id
}, function() { // successfully canceled }, function() { // successfully canceled
let index; let index;
growl.success(_t('app.public.events_show.reservation_was_successfully_cancelled')); growl.success(_t('app.public.events_show.reservation_was_successfully_cancelled'));
index = $scope.reservations.indexOf(reservation); index = $scope.reservations.indexOf(reservation);
$scope.event.nb_free_places = $scope.event.nb_free_places + reservation.total_booked_seats; $scope.event.nb_free_places = $scope.event.nb_free_places + reservation.total_booked_seats;
$scope.reservations[index].slots[0].canceled_at = new Date(); $scope.reservations[index].slots_attributes[0].canceled_at = new Date();
}, function(error) { }, function(error) {
growl.warning(_t('app.public.events_show.cancellation_failed')); growl.warning(_t('app.public.events_show.cancellation_failed'));
}); });
@ -410,11 +410,11 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
/** /**
* Test if the provided reservation has been cancelled * Test if the provided reservation has been cancelled
* @param reservation {Reservation} * @param reservation {{slots_attributes: [{canceled_at: string}]}}
* @returns {boolean} * @returns {boolean}
*/ */
$scope.isCancelled = function(reservation) { $scope.isCancelled = function(reservation) {
return !!(reservation.slots[0].canceled_at); return !!(reservation.slots_attributes[0].canceled_at);
} }
/** /**
@ -451,10 +451,9 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
return eventToPlace = e; return eventToPlace = e;
} }
}); });
$scope.reservation.slots[0].start_at = eventToPlace.start_date; $scope.reservation.slots_attributes[0].start_at = eventToPlace.start_date;
$scope.reservation.slots[0].end_at = eventToPlace.end_date; $scope.reservation.slots_attributes[0].end_at = eventToPlace.end_date;
$scope.reservation.slots[0].availability_id = eventToPlace.availability_id; $scope.reservation.slots_attributes[0].availability_id = eventToPlace.availability_id;
$scope.reservation.slots_attributes = $scope.reservation.slots;
$scope.attempting = true; $scope.attempting = true;
Reservation.update({ id: reservation.id }, { reservation: $scope.reservation }, function (reservation) { Reservation.update({ id: reservation.id }, { reservation: $scope.reservation }, function (reservation) {
$uibModalInstance.close(reservation); $uibModalInstance.close(reservation);
@ -491,10 +490,10 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
/** /**
* Checks if the provided reservation is able to be moved (date change) * Checks if the provided reservation is able to be moved (date change)
* @param reservation {{slots:[], total_booked_seats:number}} * @param reservation {{slots_attributes:[], total_booked_seats:number}}
*/ */
$scope.reservationCanModify = function (reservation) { $scope.reservationCanModify = function (reservation) {
const slotStart = moment(reservation.slots[0].start_at); const slotStart = moment(reservation.slots_attributes[0].start_at);
const now = moment(); const now = moment();
let isAble = false; let isAble = false;
@ -506,12 +505,11 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
/** /**
* Checks if the provided reservation is able to be cancelled * Checks if the provided reservation is able to be cancelled
* @param reservation {{slots:[]}} * @param reservation {{slots_attributes:[]}}
*/ */
$scope.reservationCanCancel = function(reservation) { $scope.reservationCanCancel = function(reservation) {
var now, slotStart; const slotStart = moment(reservation.slots_attributes[0].start_at);
slotStart = moment(reservation.slots[0].start_at); const now = moment();
now = moment();
return $scope.enableBookingCancel && slotStart.diff(now, "hours") >= $scope.cancelBookingDelay; return $scope.enableBookingCancel && slotStart.diff(now, "hours") >= $scope.cancelBookingDelay;
}; };
@ -554,6 +552,28 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
} }
}; };
/**
* This will open/close the online payment modal
*/
$scope.toggleOnlinePaymentModal = (beforeApply) => {
setTimeout(() => {
$scope.onlinePayment.showModal = !$scope.onlinePayment.showModal;
if (typeof beforeApply === 'function') {
beforeApply();
}
$scope.$apply();
}, 50);
};
/**
* Invoked atfer a successful card payment
* @param reservation {*} reservation
*/
$scope.afterOnlinePaymentSuccess = (reservation) => {
$scope.toggleOnlinePaymentModal();
afterPayment(reservation);
};
/* PRIVATE SCOPE */ /* PRIVATE SCOPE */
/** /**
@ -570,7 +590,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
// initialize the "reserve" object with the event's data // initialize the "reserve" object with the event's data
resetEventReserve(); resetEventReserve();
// if non-admin, get the current user's reservations into $scope.reservations // get the current user's reservations into $scope.reservations
if ($scope.currentUser) { if ($scope.currentUser) {
getReservations($scope.event.id, 'Event', $scope.currentUser.id); getReservations($scope.event.id, 'Event', $scope.currentUser.id);
} }
@ -677,71 +697,15 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
* Open a modal window which trigger the stripe payment process * Open a modal window which trigger the stripe payment process
* @param reservation {Object} to book * @param reservation {Object} to book
*/ */
const payByStripe = function (reservation) { const payOnline = function (reservation) {
$uibModal.open({ // check that the online payment is enabled
templateUrl: '/stripe/payment_modal.html', if (settingsPromise.online_payment_module !== 'true') {
size: 'md', growl.error(_t('app.shared.cart.online_payment_disabled'));
resolve: { } else {
reservation () { $scope.toggleOnlinePaymentModal(() => {
return reservation; $scope.onlinePayment.cartItems = mkCartItems(reservation, 'card');
}, });
price () { }
return Price.compute(mkCartItems(reservation, $scope.coupon.applied, 'card')).$promise;
},
wallet () {
return Wallet.getWalletByUser({ user_id: $scope.ctrl.member.id }).$promise;
},
cgv () {
return CustomAsset.get({ name: 'cgv-file' }).$promise;
},
objectToPay () {
return {
eventToReserve: $scope.event,
reserve: $scope.reserve,
member: $scope.ctrl.member
};
},
coupon () {
return $scope.coupon.applied;
},
cartItems () {
return mkCartItems(reservation, $scope.coupon.applied, 'card');
},
stripeKey: ['Setting', function (Setting) { return Setting.get({ name: 'stripe_public_key' }).$promise; }]
},
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', 'growl', 'wallet', 'helpers', '$filter', 'coupon', 'cartItems', 'stripeKey',
function ($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation, growl, wallet, helpers, $filter, coupon, cartItems, stripeKey) {
// User's wallet amount
$scope.wallet = wallet;
// Price
$scope.price = price.price;
// Amount to pay
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount);
// Cart items
$scope.cartItems = cartItems;
// CGV
$scope.cgv = cgv.custom_asset;
// Reservation
$scope.reservation = reservation;
// Used in wallet info template to interpolate some translations
$scope.numberFilter = $filter('number');
// stripe publishable key
$scope.stripeKey = stripeKey.setting.value;
// Callback to handle the post-payment and reservation
$scope.onPaymentSuccess = function (reservation) {
$uibModalInstance.close(reservation);
};
}
]
}).result['finally'](null).then(function (reservation) { afterPayment(reservation); });
}; };
/** /**
@ -749,7 +713,6 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
* @param reservation {Object} to book * @param reservation {Object} to book
*/ */
const payOnSite = function (reservation) { const payOnSite = function (reservation) {
// FIXME, this may be broken, see cart.js#payOnSite
$uibModal.open({ $uibModal.open({
templateUrl: '/shared/valid_reservation_modal.html', templateUrl: '/shared/valid_reservation_modal.html',
size: 'sm', size: 'sm',

View File

@ -66,18 +66,6 @@
<select-member></select-member> <select-member></select-member>
</div> </div>
<cart slot="reserve"
slot-selection-time="selectionTime"
events="events"
user="ctrl.member"
settings="settings"
after-payment="afterPayment"
reservable-id="{{event.id}}"
reservable-type="Event"
reservable-name="{{event.title}}"
limit-to-one-slot="true"></cart>
<section class="widget panel b-a m m-t-lg"> <section class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b small"> <div class="panel-heading b-b small">
<h3 translate>{{ 'app.public.events_show.information_and_booking' }}</h3> <h3 translate>{{ 'app.public.events_show.information_and_booking' }}</h3>
@ -215,5 +203,13 @@
</div> </div>
<div ng-if="onlinePayment.showModal">
<payment-modal is-open="onlinePayment.showModal"
toggle-modal="toggleOnlinePaymentModal"
after-success="afterOnlinePaymentSuccess"
cart-items="onlinePayment.cartItems"
current-user="currentUser"
customer="ctrl.member"/>
</div>
</div> </div>

View File

@ -1,55 +0,0 @@
<div xmlns:stripe="http://www.w3.org/1999/xhtml">
<div class="modal-header">
<img ng-src="{{logoBlack.custom_asset_file_attributes.attachment_url}}" alt="{{logo.custom_asset_file_attributes.attachment}}" class="modal-logo"/>
<h1 translate>{{ 'app.shared.stripe.online_payment' }}</h1>
</div>
<div class="modal-body">
<uib-alert ng-repeat="alert in alerts" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</uib-alert>
<div class="panel panel-default bg-light m-n">
<form name="stripeForm" stripe:form cart-items="cartItems" on-payment-success="onPaymentSuccess" stripe-key="{{stripeKey}}" class="form-horizontal">
<div class="panel-body">
<div class="row">
<wallet-info current-user="currentUser"
cart-items="cartItems"
price="price"
wallet="wallet"/>
</div>
<div id="card-element"></div>
<div id="card-errors" role="alert"></div>
<div class="form-group" ng-class="{'has-error': stripeForm.acceptCondition.$dirty && stripeForm.acceptCondition.$invalid}" ng-show="cgv">
<div class="col-sm-12 text-sm checkbox-group">
<input type="checkbox" name="acceptCondition" id="acceptCondition" ng-model="acceptCondition" value="true" ng-required="cgv != null"/>
<label for="acceptCondition">{{ 'app.shared.stripe.i_have_read_and_accept_' | translate }}
<a href="{{cgv.custom_asset_file_attributes.attachment_url}}" target="_blank" translate>{{ 'app.shared.stripe._the_general_terms_and_conditions' }}</a>
</label>
</div>
<div ng-if="!cgv">
<input type="hidden" name="acceptCondition" ng-model="acceptCondition" value="true">
</div>
</div>
<div ng-if="schedule">
<p translate translate-values="{DEADLINES: schedule.items.length}">{{ 'app.shared.stripe.payment_schedule' }}</p>
</div>
</div>
<div class="panel-footer no-padder">
<button type="submit" class="btn btn-valid btn-info btn-block p-l btn-lg text-u-c r-b text-base" ng-disabled="stripeForm.$invalid || attempting" translate translate-values="{AMOUNT:(amount | currency)}">{{ 'app.shared.stripe.confirm_payment_of_'}}</button>
</div>
</form>
</div>
</div>
<div class="modal-footer text-center">
<i class="fa fa-lock fa-2x m-r-sm pos-rlt" style="top:7px; color:#9edd78;"></i>
<img src="../../images/powered_by_stripe.png" class="m-r-sm" />
<img src="../../images/mastercard.png" class="m-r-sm" />
<img src="../../images/visa.png" class="m-r-sm" />
</div>
</div>

View File

@ -8,7 +8,7 @@ class CartItem::EventReservation < CartItem::Reservation
raise TypeError unless event.is_a? Event raise TypeError unless event.is_a? Event
super(customer, operator, event, slots) super(customer, operator, event, slots)
@normal_tickets = normal_tickets @normal_tickets = normal_tickets || 0
@other_tickets = other_tickets || [] @other_tickets = other_tickets || []
end end

View File

@ -16,5 +16,5 @@ end
json.items payment_schedule.payment_schedule_items do |item| json.items payment_schedule.payment_schedule_items do |item|
json.extract! item, :id, :due_date, :state, :invoice_id, :payment_method json.extract! item, :id, :due_date, :state, :invoice_id, :payment_method
json.amount item.amount / 100.00 json.amount item.amount / 100.00
json.client_secret item.payment_intent.client_secret if item.stp_invoice_id && item.state == 'requires_action' json.client_secret item.payment_intent.client_secret if item.payment_gateway_object && item.state == 'requires_action'
end end

View File

@ -1,8 +1,10 @@
# frozen_string_literal: true
json.id reservation.id json.id reservation.id
json.user_id reservation.statistic_profile.user_id json.user_id reservation.statistic_profile.user_id
json.user_full_name reservation.user.profile.full_name json.user_full_name reservation.user.profile.full_name
json.message reservation.message json.message reservation.message
json.slots reservation.slots do |s| json.slots_attributes reservation.slots do |s|
json.id s.id json.id s.id
json.start_at s.start_at.iso8601 json.start_at s.start_at.iso8601
json.end_at s.end_at.iso8601 json.end_at s.end_at.iso8601

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
json.id @reservation.id json.id @reservation.id
json.user_id @reservation.statistic_profile.user_id json.user_id @reservation.statistic_profile.user_id
json.user do json.user do
@ -16,7 +18,7 @@ json.user do
end end
end end
json.message @reservation.message json.message @reservation.message
json.slots @reservation.slots do |s| json.slots_attributes @reservation.slots do |s|
json.id s.id json.id s.id
json.start_at s.start_at.iso8601 json.start_at s.start_at.iso8601
json.end_at s.end_at.iso8601 json.end_at s.end_at.iso8601