mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-11 05:54:15 +01:00
compute payment schedule + basic display
This commit is contained in:
parent
506d9dd5fb
commit
21bd1312bc
@ -5,6 +5,7 @@
|
|||||||
- Fix a bug: unable to access embedded plan views
|
- Fix a bug: unable to access embedded plan views
|
||||||
- Fix a bug: warning message overflow in credit wallet modal
|
- Fix a bug: warning message overflow in credit wallet modal
|
||||||
- [TODO DEPLOY] `rails fablab:stripe:plans_prices`
|
- [TODO DEPLOY] `rails fablab:stripe:plans_prices`
|
||||||
|
- [TODO DEPLOY] `rails fablab:maintenance:rebuild_stylesheet`
|
||||||
|
|
||||||
## v4.6.3 2020 October 28
|
## v4.6.3 2020 October 28
|
||||||
|
|
||||||
|
2
Procfile
2
Procfile
@ -1,4 +1,4 @@
|
|||||||
web: bundle exec rails server puma -p $PORT
|
#web: bundle exec rails server puma -p $PORT
|
||||||
worker: bundle exec sidekiq -C ./config/sidekiq.yml
|
worker: bundle exec sidekiq -C ./config/sidekiq.yml
|
||||||
wp-client: bin/webpack-dev-server
|
wp-client: bin/webpack-dev-server
|
||||||
wp-server: SERVER_BUNDLE_ONLY=yes bin/webpack --watch
|
wp-server: SERVER_BUNDLE_ONLY=yes bin/webpack --watch
|
||||||
|
@ -155,10 +155,10 @@ class API::PaymentsController < API::ApiController
|
|||||||
current_user,
|
current_user,
|
||||||
reservable,
|
reservable,
|
||||||
slots,
|
slots,
|
||||||
plan_id,
|
plan_id: plan_id,
|
||||||
nb_places,
|
nb_places: nb_places,
|
||||||
tickets,
|
tickets: tickets,
|
||||||
coupon_params[:coupon_code])
|
coupon_code: coupon_params[:coupon_code])
|
||||||
|
|
||||||
# Subtract wallet amount from total
|
# Subtract wallet amount from total
|
||||||
total = price_details[:total]
|
total = price_details[:total]
|
||||||
|
@ -41,18 +41,23 @@ class API::PricesController < API::ApiController
|
|||||||
# user
|
# user
|
||||||
user = User.find(price_parameters[:user_id])
|
user = User.find(price_parameters[:user_id])
|
||||||
# reservable
|
# reservable
|
||||||
if [nil, ''].include? price_parameters[:reservable_id]
|
if [nil, ''].include?(price_parameters[:reservable_id]) && ['', nil].include?(price_parameters[:plan_id])
|
||||||
@amount = { elements: nil, total: 0, before_coupon: 0 }
|
@amount = { elements: nil, total: 0, before_coupon: 0 }
|
||||||
else
|
else
|
||||||
reservable = price_parameters[:reservable_type].constantize.find(price_parameters[:reservable_id])
|
reservable = if [nil, ''].include?(price_parameters[:reservable_id])
|
||||||
|
nil
|
||||||
|
else
|
||||||
|
price_parameters[:reservable_type].constantize.find(price_parameters[:reservable_id])
|
||||||
|
end
|
||||||
@amount = Price.compute(current_user.admin? || (current_user.manager? && current_user.id != user.id),
|
@amount = Price.compute(current_user.admin? || (current_user.manager? && current_user.id != user.id),
|
||||||
user,
|
user,
|
||||||
reservable,
|
reservable,
|
||||||
price_parameters[:slots_attributes] || [],
|
price_parameters[:slots_attributes] || [],
|
||||||
price_parameters[:plan_id],
|
plan_id: price_parameters[:plan_id],
|
||||||
price_parameters[:nb_reserve_places],
|
nb_places: price_parameters[:nb_reserve_places],
|
||||||
price_parameters[:tickets_attributes],
|
tickets: price_parameters[:tickets_attributes],
|
||||||
coupon_params[:coupon_code])
|
coupon_code: coupon_params[:coupon_code],
|
||||||
|
payment_schedule: price_parameters[:payment_schedule])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@ -70,7 +75,7 @@ class API::PricesController < API::ApiController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def compute_price_params
|
def compute_price_params
|
||||||
params.require(:reservation).permit(:reservable_id, :reservable_type, :plan_id, :user_id, :nb_reserve_places,
|
params.require(:reservation).permit(:reservable_id, :reservable_type, :plan_id, :user_id, :nb_reserve_places, :payment_schedule,
|
||||||
tickets_attributes: %i[event_price_category_id booked],
|
tickets_attributes: %i[event_price_category_id booked],
|
||||||
slots_attributes: %i[id start_at end_at availability_id offered])
|
slots_attributes: %i[id start_at end_at availability_id offered])
|
||||||
end
|
end
|
||||||
|
@ -65,10 +65,10 @@ class API::ReservationsController < API::ApiController
|
|||||||
user,
|
user,
|
||||||
reservation_params[:reservable_type].constantize.find(reservation_params[:reservable_id]),
|
reservation_params[:reservable_type].constantize.find(reservation_params[:reservable_id]),
|
||||||
reservation_params[:slots_attributes] || [],
|
reservation_params[:slots_attributes] || [],
|
||||||
reservation_params[:plan_id],
|
plan_id: reservation_params[:plan_id],
|
||||||
reservation_params[:nb_reserve_places],
|
nb_places: reservation_params[:nb_reserve_places],
|
||||||
reservation_params[:tickets_attributes],
|
tickets: reservation_params[:tickets_attributes],
|
||||||
coupon_params[:coupon_code])
|
coupon_code: coupon_params[:coupon_code])
|
||||||
|
|
||||||
# Subtract wallet amount from total
|
# Subtract wallet amount from total
|
||||||
total = price_details[:total]
|
total = price_details[:total]
|
||||||
|
@ -54,10 +54,10 @@ class API::SubscriptionsController < API::ApiController
|
|||||||
user,
|
user,
|
||||||
nil,
|
nil,
|
||||||
[],
|
[],
|
||||||
subscription_params[:plan_id],
|
plan_id: subscription_params[:plan_id],
|
||||||
nil,
|
nb_places: nil,
|
||||||
nil,
|
tickets: nil,
|
||||||
coupon_params[:coupon_code])
|
coupon_code: coupon_params[:coupon_code])
|
||||||
|
|
||||||
# Subtract wallet amount from total
|
# Subtract wallet amount from total
|
||||||
total = price_details[:total]
|
total = price_details[:total]
|
||||||
|
@ -12,8 +12,8 @@
|
|||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScope', '$state', '$uibModal', 'Auth', 'AuthService', 'dialogs', 'growl', 'plansPromise', 'groupsPromise', 'Subscription', 'Member', 'subscriptionExplicationsPromise', '_t', 'Wallet', 'helpers', 'settingsPromise',
|
Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScope', '$state', '$uibModal', 'Auth', 'AuthService', 'dialogs', 'growl', 'plansPromise', 'groupsPromise', 'Subscription', 'Member', 'subscriptionExplicationsPromise', '_t', 'Wallet', 'helpers', 'settingsPromise', 'Price',
|
||||||
function ($scope, $rootScope, $state, $uibModal, Auth, AuthService, dialogs, growl, plansPromise, groupsPromise, Subscription, Member, subscriptionExplicationsPromise, _t, Wallet, helpers, settingsPromise) {
|
function ($scope, $rootScope, $state, $uibModal, Auth, AuthService, dialogs, growl, plansPromise, groupsPromise, Subscription, Member, subscriptionExplicationsPromise, _t, Wallet, helpers, settingsPromise, Price) {
|
||||||
/* PUBLIC SCOPE */
|
/* PUBLIC SCOPE */
|
||||||
|
|
||||||
// list of groups
|
// list of groups
|
||||||
@ -46,9 +46,12 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
|
|||||||
$scope.coupon =
|
$scope.coupon =
|
||||||
{ applied: null };
|
{ applied: null };
|
||||||
|
|
||||||
// Storage for the total price (plan price + coupon, if any)
|
// Storage for the total price (plan price + coupon, if any) and of the payment schedule
|
||||||
$scope.cart =
|
$scope.cart = {
|
||||||
{ total: null };
|
total: null,
|
||||||
|
payment_schedule: false,
|
||||||
|
schedule: null
|
||||||
|
};
|
||||||
|
|
||||||
// text that appears in the bottom-right box of the page (subscriptions rules details)
|
// text that appears in the bottom-right box of the page (subscriptions rules details)
|
||||||
$scope.subscriptionExplicationsAlert = subscriptionExplicationsPromise.setting.value;
|
$scope.subscriptionExplicationsAlert = subscriptionExplicationsPromise.setting.value;
|
||||||
@ -76,6 +79,7 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
|
|||||||
if ($scope.isAuthenticated()) {
|
if ($scope.isAuthenticated()) {
|
||||||
if ($scope.selectedPlan !== plan) {
|
if ($scope.selectedPlan !== plan) {
|
||||||
$scope.selectedPlan = plan;
|
$scope.selectedPlan = plan;
|
||||||
|
$scope.cart.payment_schedule = plan.monthly_payment;
|
||||||
updateCartPrice();
|
updateCartPrice();
|
||||||
} else {
|
} else {
|
||||||
$scope.selectedPlan = null;
|
$scope.selectedPlan = null;
|
||||||
@ -87,6 +91,18 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
|
|||||||
}, 50);
|
}, 50);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will update the payment_schedule setting when the user toggles the switch button
|
||||||
|
* @param checked {Boolean}
|
||||||
|
*/
|
||||||
|
$scope.togglePaymentSchedule = (checked) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
$scope.cart.payment_schedule = checked;
|
||||||
|
updateCartPrice();
|
||||||
|
$scope.$apply();
|
||||||
|
}, 50);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the provided plan is currently selected
|
* Check if the provided plan is currently selected
|
||||||
* @param plan {Object} Resource plan
|
* @param plan {Object} Resource plan
|
||||||
@ -101,17 +117,17 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
|
|||||||
$scope.openSubscribePlanModal = function () {
|
$scope.openSubscribePlanModal = function () {
|
||||||
Wallet.getWalletByUser({ user_id: $scope.ctrl.member.id }, function (wallet) {
|
Wallet.getWalletByUser({ user_id: $scope.ctrl.member.id }, function (wallet) {
|
||||||
const amountToPay = helpers.getAmountToPay($scope.cart.total, wallet.amount);
|
const amountToPay = helpers.getAmountToPay($scope.cart.total, wallet.amount);
|
||||||
if ((AuthService.isAuthorized('member') && amountToPay > 0)
|
if ((AuthService.isAuthorized('member') && amountToPay > 0) ||
|
||||||
|| (AuthService.isAuthorized('manager') && $scope.ctrl.member.id === $rootScope.currentUser.id && amountToPay > 0)) {
|
(AuthService.isAuthorized('manager') && $scope.ctrl.member.id === $rootScope.currentUser.id && amountToPay > 0)) {
|
||||||
if (settingsPromise.online_payment_module !== 'true') {
|
if (settingsPromise.online_payment_module !== 'true') {
|
||||||
growl.error(_t('app.public.plans.online_payment_disabled'));
|
growl.error(_t('app.public.plans.online_payment_disabled'));
|
||||||
} else {
|
} else {
|
||||||
return payByStripe();
|
return payByStripe();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (AuthService.isAuthorized('admin')
|
if (AuthService.isAuthorized('admin') ||
|
||||||
|| (AuthService.isAuthorized('manager') && $scope.ctrl.member.id !== $rootScope.currentUser.id)
|
(AuthService.isAuthorized('manager') && $scope.ctrl.member.id !== $rootScope.currentUser.id) ||
|
||||||
|| amountToPay === 0) {
|
amountToPay === 0) {
|
||||||
return payOnSite();
|
return payOnSite();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -191,7 +207,7 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
|
|||||||
// group all plans by Group
|
// group all plans by Group
|
||||||
for (const group of $scope.groups) {
|
for (const group of $scope.groups) {
|
||||||
const groupObj = { id: group.id, name: group.name, plans: [], actives: 0 };
|
const groupObj = { id: group.id, name: group.name, plans: [], actives: 0 };
|
||||||
for (let plan of plansPromise) {
|
for (const plan of plansPromise) {
|
||||||
if (plan.group_id === group.id) {
|
if (plan.group_id === group.id) {
|
||||||
groupObj.plans.push(plan);
|
groupObj.plans.push(plan);
|
||||||
if (!plan.disabled) { groupObj.actives++; }
|
if (!plan.disabled) { groupObj.actives++; }
|
||||||
@ -225,22 +241,44 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
|
|||||||
const updateCartPrice = function () {
|
const updateCartPrice = function () {
|
||||||
// first we check the selection of a user
|
// first we check the selection of a user
|
||||||
if (Object.keys($scope.ctrl.member).length > 0 && $scope.selectedPlan) {
|
if (Object.keys($scope.ctrl.member).length > 0 && $scope.selectedPlan) {
|
||||||
$scope.cart.total = $scope.selectedPlan.amount;
|
const r = mkReservation($scope.ctrl.member, $scope.selectedPlan);
|
||||||
// apply the coupon if any
|
Price.compute(mkRequestParams(r, $scope.coupon.applied), function (res) {
|
||||||
if ($scope.coupon.applied) {
|
$scope.cart.total = res.price;
|
||||||
let discount;
|
$scope.cart.schedule = res.schedule;
|
||||||
if ($scope.coupon.applied.type === 'percent_off') {
|
});
|
||||||
discount = ($scope.cart.total * $scope.coupon.applied.percent_off) / 100;
|
|
||||||
} else if ($scope.coupon.applied.type === 'amount_off') {
|
|
||||||
discount = $scope.coupon.applied.amount_off;
|
|
||||||
}
|
|
||||||
return $scope.cart.total -= discount;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return $scope.reserve.amountTotal = null;
|
return $scope.reserve.amountTotal = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format the parameters expected by /api/prices/compute or /api/reservations and return the resulting object
|
||||||
|
* @param reservation {Object} as returned by mkReservation()
|
||||||
|
* @param coupon {Object} Coupon as returned from the API
|
||||||
|
* @return {{reservation:Object, coupon_code:string}}
|
||||||
|
*/
|
||||||
|
const mkRequestParams = function (reservation, coupon) {
|
||||||
|
return {
|
||||||
|
reservation,
|
||||||
|
coupon_code: ((coupon ? coupon.code : undefined))
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an hash map implementing the Reservation specs
|
||||||
|
* @param member {Object} User as retrieved from the API: current user / selected user if current is admin
|
||||||
|
* @param [plan] {Object} Plan as retrieved from the API: plan to buy with the current reservation
|
||||||
|
* @return {{user_id:Number, slots_attributes:Array<Object>, plan_id:Number|null}}
|
||||||
|
*/
|
||||||
|
const mkReservation = function (member, plan) {
|
||||||
|
return {
|
||||||
|
user_id: member.id,
|
||||||
|
slots_attributes: [],
|
||||||
|
plan_id: ((plan ? plan.id : undefined)),
|
||||||
|
payment_schedule: $scope.cart.payment_schedule
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a modal window which trigger the stripe payment process
|
* Open a modal window which trigger the stripe payment process
|
||||||
*/
|
*/
|
||||||
@ -296,7 +334,7 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}).result['finally'](null).then(function (subscription) {
|
}).result.finally(null).then(function (subscription) {
|
||||||
$scope.ctrl.member.subscribed_plan = angular.copy($scope.selectedPlan);
|
$scope.ctrl.member.subscribed_plan = angular.copy($scope.selectedPlan);
|
||||||
Auth._currentUser.subscribed_plan = angular.copy($scope.selectedPlan);
|
Auth._currentUser.subscribed_plan = angular.copy($scope.selectedPlan);
|
||||||
$scope.paid.plan = angular.copy($scope.selectedPlan);
|
$scope.paid.plan = angular.copy($scope.selectedPlan);
|
||||||
@ -383,7 +421,7 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
|
|||||||
$scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
|
$scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}).result['finally'](null).then(function (subscription) {
|
}).result.finally(null).then(function (subscription) {
|
||||||
$scope.ctrl.member.subscribed_plan = angular.copy($scope.selectedPlan);
|
$scope.ctrl.member.subscribed_plan = angular.copy($scope.selectedPlan);
|
||||||
Auth._currentUser.subscribed_plan = angular.copy($scope.selectedPlan);
|
Auth._currentUser.subscribed_plan = angular.copy($scope.selectedPlan);
|
||||||
$scope.ctrl.member = null;
|
$scope.ctrl.member = null;
|
||||||
|
@ -118,10 +118,24 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<coupon show="!ctrl.member.subscribed_plan" coupon="coupon.applied" total="selectedPlan.amount" user-id="{{ctrl.member.id}}"></coupon>
|
<coupon show="!ctrl.member.subscribed_plan" coupon="coupon.applied" total="selectedPlan.amount" user-id="{{ctrl.member.id}}"></coupon>
|
||||||
|
|
||||||
|
<label for="payment_schedule" translate>{{ 'app.public.plans.monthly_payment' }}</label>
|
||||||
|
<switch checked="cart.payment_schedule" id="payment_schedule" on-change="togglePaymentSchedule" class-name="'v-middle'" ng-if="selectedPlan.monthly_payment"></switch>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="widget-footer">
|
<div class="widget-footer">
|
||||||
<button class="btn btn-valid btn-info btn-block p-l text-u-c r-b" ng-click="openSubscribePlanModal()" ng-if="!ctrl.member.subscribed_plan">{{ 'app.public.plans.confirm_and_pay' | translate }} {{cart.total | currency}}</button>
|
<div ng-if="cart.schedule">
|
||||||
|
<h4 translate>{{ 'app.public.plans.your_payment_schedule' }}</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="item in cart.schedule.items">
|
||||||
|
<span>{{item.due_date | amDateFormat:'L'}}</span>
|
||||||
|
<span>{{item.price | currency}}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-valid btn-info btn-block p-l text-u-c r-b" ng-click="openSubscribePlanModal()" ng-if="!ctrl.member.subscribed_plan">
|
||||||
|
{{ 'app.public.plans.confirm_and_pay' | translate }} {{cart.total | currency}}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
@ -20,13 +20,15 @@ class Price < ApplicationRecord
|
|||||||
# @param user {User} The user who's reserving (or selected if an admin is reserving)
|
# @param user {User} The user who's reserving (or selected if an admin is reserving)
|
||||||
# @param reservable {Machine|Training|Event} what the reservation is targeting
|
# @param reservable {Machine|Training|Event} what the reservation is targeting
|
||||||
# @param slots {Array<Slot>} when did the reservation will occur
|
# @param slots {Array<Slot>} when did the reservation will occur
|
||||||
# @param [plan_id] {Number} if the user is subscribing to a plan at the same time of his reservation, specify the plan's ID here
|
# @param options {plan_id:Number, nb_places:Number, tickets:Array<Ticket>, coupon_code:String, payment_schedule:Boolean}
|
||||||
# @param [nb_places] {Number} for _reservable_ of type Event, pass here the number of booked places
|
# - plan_id {Number} if the user is subscribing to a plan at the same time of his reservation, specify the plan's ID here
|
||||||
# @param [tickets] {Array<Ticket>} for _reservable_ of type Event, mapping of the number of seats booked per price's category
|
# - nb_places {Number} for _reservable_ of type Event, pass here the number of booked places
|
||||||
# @param [coupon_code] {String} Code of the coupon to apply to the total price
|
# - tickets {Array<Ticket>} for _reservable_ of type Event, mapping of the number of seats booked per price's category
|
||||||
|
# - coupon_code {String} Code of the coupon to apply to the total price
|
||||||
|
# - payment_schedule {Boolean} if the user is requesting a payment schedule for his subscription
|
||||||
# @return {Hash} total and price detail
|
# @return {Hash} total and price detail
|
||||||
##
|
##
|
||||||
def compute(admin, user, reservable, slots, plan_id = nil, nb_places = nil, tickets = nil, coupon_code = nil)
|
def compute(admin, user, reservable, slots, options = {})
|
||||||
total_amount = 0
|
total_amount = 0
|
||||||
all_elements = {}
|
all_elements = {}
|
||||||
all_elements[:slots] = []
|
all_elements[:slots] = []
|
||||||
@ -35,9 +37,9 @@ class Price < ApplicationRecord
|
|||||||
plan = if user.subscribed_plan
|
plan = if user.subscribed_plan
|
||||||
new_plan_being_bought = false
|
new_plan_being_bought = false
|
||||||
user.subscribed_plan
|
user.subscribed_plan
|
||||||
elsif plan_id
|
elsif options[:plan_id]
|
||||||
new_plan_being_bought = true
|
new_plan_being_bought = true
|
||||||
Plan.find(plan_id)
|
Plan.find(options[:plan_id])
|
||||||
else
|
else
|
||||||
new_plan_being_bought = false
|
new_plan_being_bought = false
|
||||||
nil
|
nil
|
||||||
@ -91,8 +93,8 @@ class Price < ApplicationRecord
|
|||||||
|
|
||||||
# Event reservation
|
# Event reservation
|
||||||
when Event
|
when Event
|
||||||
amount = reservable.amount * nb_places
|
amount = reservable.amount * options[:nb_places]
|
||||||
tickets&.each do |ticket|
|
options[:tickets]&.each do |ticket|
|
||||||
amount += ticket[:booked] * EventPriceCategory.find(ticket[:event_price_category_id]).amount
|
amount += ticket[:booked] * EventPriceCategory.find(ticket[:event_price_category_id]).amount
|
||||||
end
|
end
|
||||||
slots.each do |slot|
|
slots.each do |slot|
|
||||||
@ -130,8 +132,8 @@ class Price < ApplicationRecord
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
end
|
end
|
||||||
|
|
||||||
# === compute Plan price if any ===
|
# === compute Plan price (if any) ===
|
||||||
unless plan_id.nil?
|
unless options[:plan_id].nil?
|
||||||
all_elements[:plan] = plan.amount
|
all_elements[:plan] = plan.amount
|
||||||
total_amount += plan.amount
|
total_amount += plan.amount
|
||||||
end
|
end
|
||||||
@ -139,11 +141,29 @@ class Price < ApplicationRecord
|
|||||||
# === apply Coupon if any ===
|
# === apply Coupon if any ===
|
||||||
_amount_no_coupon = total_amount
|
_amount_no_coupon = total_amount
|
||||||
cs = CouponService.new
|
cs = CouponService.new
|
||||||
cp = cs.validate(coupon_code, user.id)
|
cp = cs.validate(options[:coupon_code], user.id)
|
||||||
total_amount = cs.apply(total_amount, cp)
|
total_amount = cs.apply(total_amount, cp)
|
||||||
|
|
||||||
|
# == generate PaymentSchedule ()if applicable) ===
|
||||||
|
schedule = if options[:payment_schedule] && plan.monthly_payment
|
||||||
|
pss = PaymentScheduleService.new
|
||||||
|
pss.compute(plan, _amount_no_coupon, cp)
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
if schedule
|
||||||
|
total_amount -= schedule[:payment_schedule].total
|
||||||
|
total_amount += schedule[:items][0].amount
|
||||||
|
end
|
||||||
|
|
||||||
# return result
|
# return result
|
||||||
{ elements: all_elements, total: total_amount.to_i, before_coupon: _amount_no_coupon.to_i, coupon: cp }
|
{
|
||||||
|
elements: all_elements,
|
||||||
|
total: total_amount.to_i,
|
||||||
|
before_coupon: _amount_no_coupon.to_i,
|
||||||
|
coupon: cp,
|
||||||
|
schedule: schedule
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
38
app/services/payment_schedule_service.rb
Normal file
38
app/services/payment_schedule_service.rb
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# create PaymentSchedules for various items
|
||||||
|
class PaymentScheduleService
|
||||||
|
##
|
||||||
|
# Compute a payment schedule for a new subscription to the provided plan
|
||||||
|
# @param plan {Plan}
|
||||||
|
# @param total {Number} Total amount of the current shopping cart (which includes this plan)
|
||||||
|
# @param coupon {Coupon} apply this coupon, if any
|
||||||
|
##
|
||||||
|
def compute(plan, total, coupon = nil)
|
||||||
|
price = if coupon
|
||||||
|
cs = CouponService.new
|
||||||
|
cs.ventilate(total, plan.amount, coupon)
|
||||||
|
else
|
||||||
|
plan.amount
|
||||||
|
end
|
||||||
|
ps = PaymentSchedule.new(scheduled: plan, total: price, coupon: coupon)
|
||||||
|
deadlines = plan.duration / 1.month
|
||||||
|
per_month = price / deadlines
|
||||||
|
adjustment = if per_month * deadlines != price
|
||||||
|
price - (per_month * deadlines)
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end
|
||||||
|
items = []
|
||||||
|
(0..deadlines - 1).each do |i|
|
||||||
|
date = DateTime.current + i.months
|
||||||
|
items.push PaymentScheduleItem.new(
|
||||||
|
amount: per_month + adjustment,
|
||||||
|
due_date: date,
|
||||||
|
payment_schedule: ps
|
||||||
|
)
|
||||||
|
adjustment = 0
|
||||||
|
end
|
||||||
|
{ payment_schedule: ps, items: items }
|
||||||
|
end
|
||||||
|
end
|
@ -1,10 +1,20 @@
|
|||||||
json.price @amount[:total] / 100.00
|
json.price @amount[:total] / 100.00
|
||||||
json.price_without_coupon @amount[:before_coupon] / 100.00
|
json.price_without_coupon @amount[:before_coupon] / 100.00
|
||||||
json.details do
|
if @amount[:elements]
|
||||||
|
json.details do
|
||||||
json.slots @amount[:elements][:slots] do |slot|
|
json.slots @amount[:elements][:slots] do |slot|
|
||||||
json.start_at slot[:start_at]
|
json.start_at slot[:start_at]
|
||||||
json.price slot[:price] / 100.00
|
json.price slot[:price] / 100.00
|
||||||
json.promo slot[:promo]
|
json.promo slot[:promo]
|
||||||
end
|
end
|
||||||
json.plan @amount[:elements][:plan] / 100.00 if @amount[:elements][:plan]
|
json.plan @amount[:elements][:plan] / 100.00 if @amount[:elements][:plan]
|
||||||
end if @amount[:elements]
|
end
|
||||||
|
end
|
||||||
|
if @amount[:schedule]
|
||||||
|
json.schedule do
|
||||||
|
json.items @amount[:schedule][:items] do |item|
|
||||||
|
json.price item.amount / 100.00
|
||||||
|
json.due_date item.due_date
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@ -250,7 +250,9 @@ en:
|
|||||||
your_subscription_has_expired_on_the_DATE: "Your subscription has expired on the {DATE}"
|
your_subscription_has_expired_on_the_DATE: "Your subscription has expired on the {DATE}"
|
||||||
subscription_price: "Subscription price"
|
subscription_price: "Subscription price"
|
||||||
you_ve_just_selected_a_subscription_html: "You've just selected a <strong>subscription</strong>:"
|
you_ve_just_selected_a_subscription_html: "You've just selected a <strong>subscription</strong>:"
|
||||||
|
monthly_payment: "Pay by monthly schedule"
|
||||||
confirm_and_pay: "Confirm and pay"
|
confirm_and_pay: "Confirm and pay"
|
||||||
|
your_payment_schedule: "Your payment schedule"
|
||||||
you_ve_just_payed_the_subscription_html: "You've just paid the <strong>subscription</strong>:"
|
you_ve_just_payed_the_subscription_html: "You've just paid the <strong>subscription</strong>:"
|
||||||
thank_you_your_subscription_is_successful: "Thank you. Your subscription is successful!"
|
thank_you_your_subscription_is_successful: "Thank you. Your subscription is successful!"
|
||||||
your_invoice_will_be_available_soon_from_your_dashboard: "Your invoice will be available soon from your dashboard"
|
your_invoice_will_be_available_soon_from_your_dashboard: "Your invoice will be available soon from your dashboard"
|
||||||
|
@ -250,6 +250,8 @@ fr:
|
|||||||
your_subscription_has_expired_on_the_DATE: "Votre abonnement a expiré au {DATE}"
|
your_subscription_has_expired_on_the_DATE: "Votre abonnement a expiré au {DATE}"
|
||||||
subscription_price: "Coût de l'abonnement"
|
subscription_price: "Coût de l'abonnement"
|
||||||
you_ve_just_selected_a_subscription_html: "Vous venez de sélectionner un <strong>abonnement</strong> :"
|
you_ve_just_selected_a_subscription_html: "Vous venez de sélectionner un <strong>abonnement</strong> :"
|
||||||
|
monthly_payment: "Payer par échéancier mensuel"
|
||||||
|
your_payment_schedule: "Votre échéancier de paiement"
|
||||||
confirm_and_pay: "Valider et payer"
|
confirm_and_pay: "Valider et payer"
|
||||||
you_ve_just_payed_the_subscription_html: "Vous venez de régler <strong>l'abonnement</strong> :"
|
you_ve_just_payed_the_subscription_html: "Vous venez de régler <strong>l'abonnement</strong> :"
|
||||||
thank_you_your_subscription_is_successful: "Merci. Votre abonnement a bien été pris en compte !"
|
thank_you_your_subscription_is_successful: "Merci. Votre abonnement a bien été pris en compte !"
|
||||||
|
@ -108,8 +108,8 @@ SET default_tablespace = '';
|
|||||||
|
|
||||||
CREATE TABLE public.abuses (
|
CREATE TABLE public.abuses (
|
||||||
id integer NOT NULL,
|
id integer NOT NULL,
|
||||||
signaled_id integer,
|
|
||||||
signaled_type character varying,
|
signaled_type character varying,
|
||||||
|
signaled_id integer,
|
||||||
first_name character varying,
|
first_name character varying,
|
||||||
last_name character varying,
|
last_name character varying,
|
||||||
email character varying,
|
email character varying,
|
||||||
@ -187,8 +187,8 @@ CREATE TABLE public.addresses (
|
|||||||
locality character varying,
|
locality character varying,
|
||||||
country character varying,
|
country character varying,
|
||||||
postal_code character varying,
|
postal_code character varying,
|
||||||
placeable_id integer,
|
|
||||||
placeable_type character varying,
|
placeable_type character varying,
|
||||||
|
placeable_id integer,
|
||||||
created_at timestamp without time zone,
|
created_at timestamp without time zone,
|
||||||
updated_at timestamp without time zone
|
updated_at timestamp without time zone
|
||||||
);
|
);
|
||||||
@ -263,8 +263,8 @@ CREATE TABLE public.ar_internal_metadata (
|
|||||||
|
|
||||||
CREATE TABLE public.assets (
|
CREATE TABLE public.assets (
|
||||||
id integer NOT NULL,
|
id integer NOT NULL,
|
||||||
viewable_id integer,
|
|
||||||
viewable_type character varying,
|
viewable_type character varying,
|
||||||
|
viewable_id integer,
|
||||||
attachment character varying,
|
attachment character varying,
|
||||||
type character varying,
|
type character varying,
|
||||||
created_at timestamp without time zone,
|
created_at timestamp without time zone,
|
||||||
@ -504,8 +504,8 @@ ALTER SEQUENCE public.coupons_id_seq OWNED BY public.coupons.id;
|
|||||||
|
|
||||||
CREATE TABLE public.credits (
|
CREATE TABLE public.credits (
|
||||||
id integer NOT NULL,
|
id integer NOT NULL,
|
||||||
creditable_id integer,
|
|
||||||
creditable_type character varying,
|
creditable_type character varying,
|
||||||
|
creditable_id integer,
|
||||||
plan_id integer,
|
plan_id integer,
|
||||||
hours integer,
|
hours integer,
|
||||||
created_at timestamp without time zone,
|
created_at timestamp without time zone,
|
||||||
@ -1046,8 +1046,8 @@ ALTER SEQUENCE public.invoice_items_id_seq OWNED BY public.invoice_items.id;
|
|||||||
|
|
||||||
CREATE TABLE public.invoices (
|
CREATE TABLE public.invoices (
|
||||||
id integer NOT NULL,
|
id integer NOT NULL,
|
||||||
invoiced_id integer,
|
|
||||||
invoiced_type character varying,
|
invoiced_type character varying,
|
||||||
|
invoiced_id integer,
|
||||||
stp_invoice_id character varying,
|
stp_invoice_id character varying,
|
||||||
total integer,
|
total integer,
|
||||||
created_at timestamp without time zone,
|
created_at timestamp without time zone,
|
||||||
@ -1226,15 +1226,15 @@ ALTER SEQUENCE public.machines_id_seq OWNED BY public.machines.id;
|
|||||||
CREATE TABLE public.notifications (
|
CREATE TABLE public.notifications (
|
||||||
id integer NOT NULL,
|
id integer NOT NULL,
|
||||||
receiver_id integer,
|
receiver_id integer,
|
||||||
attached_object_id integer,
|
|
||||||
attached_object_type character varying,
|
attached_object_type character varying,
|
||||||
|
attached_object_id integer,
|
||||||
notification_type_id integer,
|
notification_type_id integer,
|
||||||
is_read boolean DEFAULT false,
|
is_read boolean DEFAULT false,
|
||||||
created_at timestamp without time zone,
|
created_at timestamp without time zone,
|
||||||
updated_at timestamp without time zone,
|
updated_at timestamp without time zone,
|
||||||
receiver_type character varying,
|
receiver_type character varying,
|
||||||
is_send boolean DEFAULT false,
|
is_send boolean DEFAULT false,
|
||||||
meta_data jsonb DEFAULT '{}'::jsonb
|
meta_data jsonb DEFAULT '"{}"'::jsonb
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
@ -1653,8 +1653,8 @@ CREATE TABLE public.prices (
|
|||||||
id integer NOT NULL,
|
id integer NOT NULL,
|
||||||
group_id integer,
|
group_id integer,
|
||||||
plan_id integer,
|
plan_id integer,
|
||||||
priceable_id integer,
|
|
||||||
priceable_type character varying,
|
priceable_type character varying,
|
||||||
|
priceable_id integer,
|
||||||
amount integer,
|
amount integer,
|
||||||
created_at timestamp without time zone NOT NULL,
|
created_at timestamp without time zone NOT NULL,
|
||||||
updated_at timestamp without time zone NOT NULL
|
updated_at timestamp without time zone NOT NULL
|
||||||
@ -1969,8 +1969,8 @@ CREATE TABLE public.reservations (
|
|||||||
message text,
|
message text,
|
||||||
created_at timestamp without time zone,
|
created_at timestamp without time zone,
|
||||||
updated_at timestamp without time zone,
|
updated_at timestamp without time zone,
|
||||||
reservable_id integer,
|
|
||||||
reservable_type character varying,
|
reservable_type character varying,
|
||||||
|
reservable_id integer,
|
||||||
nb_reserve_places integer,
|
nb_reserve_places integer,
|
||||||
statistic_profile_id integer
|
statistic_profile_id integer
|
||||||
);
|
);
|
||||||
@ -2002,8 +2002,8 @@ ALTER SEQUENCE public.reservations_id_seq OWNED BY public.reservations.id;
|
|||||||
CREATE TABLE public.roles (
|
CREATE TABLE public.roles (
|
||||||
id integer NOT NULL,
|
id integer NOT NULL,
|
||||||
name character varying,
|
name character varying,
|
||||||
resource_id integer,
|
|
||||||
resource_type character varying,
|
resource_type character varying,
|
||||||
|
resource_id integer,
|
||||||
created_at timestamp without time zone,
|
created_at timestamp without time zone,
|
||||||
updated_at timestamp without time zone
|
updated_at timestamp without time zone
|
||||||
);
|
);
|
||||||
@ -2937,8 +2937,8 @@ CREATE TABLE public.users_roles (
|
|||||||
CREATE TABLE public.wallet_transactions (
|
CREATE TABLE public.wallet_transactions (
|
||||||
id integer NOT NULL,
|
id integer NOT NULL,
|
||||||
wallet_id integer,
|
wallet_id integer,
|
||||||
transactable_id integer,
|
|
||||||
transactable_type character varying,
|
transactable_type character varying,
|
||||||
|
transactable_id integer,
|
||||||
transaction_type character varying,
|
transaction_type character varying,
|
||||||
amount integer,
|
amount integer,
|
||||||
created_at timestamp without time zone NOT NULL,
|
created_at timestamp without time zone NOT NULL,
|
||||||
@ -4027,6 +4027,14 @@ ALTER TABLE ONLY public.roles
|
|||||||
ADD CONSTRAINT roles_pkey PRIMARY KEY (id);
|
ADD CONSTRAINT roles_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.schema_migrations
|
||||||
|
ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: settings settings_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
-- Name: settings settings_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@ -5084,29 +5092,6 @@ CREATE INDEX profiles_lower_unaccent_last_name_trgm_idx ON public.profiles USING
|
|||||||
CREATE INDEX projects_search_vector_idx ON public.projects USING gin (search_vector);
|
CREATE INDEX projects_search_vector_idx ON public.projects USING gin (search_vector);
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: unique_schema_migrations; Type: INDEX; Schema: public; Owner: -
|
|
||||||
--
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX unique_schema_migrations ON public.schema_migrations USING btree (version);
|
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: accounting_periods accounting_periods_del_protect; Type: RULE; Schema: public; Owner: -
|
|
||||||
--
|
|
||||||
|
|
||||||
CREATE RULE accounting_periods_del_protect AS
|
|
||||||
ON DELETE TO public.accounting_periods DO INSTEAD NOTHING;
|
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: accounting_periods accounting_periods_upd_protect; Type: RULE; Schema: public; Owner: -
|
|
||||||
--
|
|
||||||
|
|
||||||
CREATE RULE accounting_periods_upd_protect AS
|
|
||||||
ON UPDATE TO public.accounting_periods DO INSTEAD NOTHING;
|
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: projects projects_search_content_trigger; Type: TRIGGER; Schema: public; Owner: -
|
-- Name: projects projects_search_content_trigger; Type: TRIGGER; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@ -5633,7 +5618,6 @@ INSERT INTO "schema_migrations" (version) VALUES
|
|||||||
('20140605125131'),
|
('20140605125131'),
|
||||||
('20140605142133'),
|
('20140605142133'),
|
||||||
('20140605151442'),
|
('20140605151442'),
|
||||||
('20140606133116'),
|
|
||||||
('20140609092700'),
|
('20140609092700'),
|
||||||
('20140609092827'),
|
('20140609092827'),
|
||||||
('20140610153123'),
|
('20140610153123'),
|
||||||
@ -5702,14 +5686,12 @@ INSERT INTO "schema_migrations" (version) VALUES
|
|||||||
('20150507075620'),
|
('20150507075620'),
|
||||||
('20150512123546'),
|
('20150512123546'),
|
||||||
('20150520132030'),
|
('20150520132030'),
|
||||||
('20150520133409'),
|
|
||||||
('20150526130729'),
|
('20150526130729'),
|
||||||
('20150527153312'),
|
('20150527153312'),
|
||||||
('20150529113555'),
|
('20150529113555'),
|
||||||
('20150601125944'),
|
('20150601125944'),
|
||||||
('20150603104502'),
|
('20150603104502'),
|
||||||
('20150603104658'),
|
('20150603104658'),
|
||||||
('20150603133050'),
|
|
||||||
('20150604081757'),
|
('20150604081757'),
|
||||||
('20150604131525'),
|
('20150604131525'),
|
||||||
('20150608142234'),
|
('20150608142234'),
|
||||||
@ -5791,7 +5773,6 @@ INSERT INTO "schema_migrations" (version) VALUES
|
|||||||
('20160905142700'),
|
('20160905142700'),
|
||||||
('20160906094739'),
|
('20160906094739'),
|
||||||
('20160906094847'),
|
('20160906094847'),
|
||||||
('20160906145713'),
|
|
||||||
('20160915105234'),
|
('20160915105234'),
|
||||||
('20161123104604'),
|
('20161123104604'),
|
||||||
('20170109085345'),
|
('20170109085345'),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user