mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-03-21 12:29:03 +01:00
refactored subscription process + renew (update) subscription/ offre free days
This commit is contained in:
parent
e0ac9d1ac3
commit
609d19e5d1
@ -49,7 +49,7 @@ class API::PaymentsController < API::ApiController
|
||||
if params[:cart_items][:reservation]
|
||||
res = on_reservation_success(intent, amount[:details])
|
||||
elsif params[:cart_items][:subscription]
|
||||
res = on_subscription_success(intent)
|
||||
res = on_subscription_success(intent, amount[:details])
|
||||
end
|
||||
end
|
||||
|
||||
@ -84,7 +84,7 @@ class API::PaymentsController < API::ApiController
|
||||
if params[:cart_items][:reservation]
|
||||
res = on_reservation_success(intent, amount[:details])
|
||||
elsif params[:cart_items][:subscription]
|
||||
res = on_subscription_success(intent)
|
||||
res = on_subscription_success(intent, amount[:details])
|
||||
end
|
||||
end
|
||||
|
||||
@ -125,7 +125,7 @@ class API::PaymentsController < API::ApiController
|
||||
end
|
||||
end
|
||||
|
||||
def on_subscription_success(intent)
|
||||
def on_subscription_success(intent, details)
|
||||
@subscription = Subscription.new(subscription_params)
|
||||
user_id = if current_user.admin? || current_user.manager?
|
||||
params[:cart_items][:subscription][:user_id]
|
||||
@ -134,8 +134,7 @@ class API::PaymentsController < API::ApiController
|
||||
end
|
||||
is_subscribe = Subscriptions::Subscribe.new(current_user.invoicing_profile.id, user_id)
|
||||
.pay_and_save(@subscription,
|
||||
coupon: coupon_params[:coupon_code],
|
||||
invoice: true,
|
||||
payment_details: details,
|
||||
payment_intent_id: intent.id,
|
||||
schedule: params[:cart_items][:subscription][:payment_schedule],
|
||||
payment_method: 'stripe')
|
||||
|
@ -14,14 +14,13 @@ class API::SubscriptionsController < API::ApiController
|
||||
# Managers can create subscriptions for other users
|
||||
def create
|
||||
user_id = current_user.admin? || current_user.manager? ? params[:subscription][:user_id] : current_user.id
|
||||
amount = transaction_amount(current_user.admin? || (current_user.manager? && current_user.id != user_id), user_id)
|
||||
transaction = transaction_amount(current_user.admin? || (current_user.manager? && current_user.id != user_id), user_id)
|
||||
|
||||
authorize SubscriptionContext.new(Subscription, amount, user_id)
|
||||
authorize SubscriptionContext.new(Subscription, transaction[:amount], user_id)
|
||||
|
||||
@subscription = Subscription.new(subscription_params)
|
||||
is_subscribe = Subscriptions::Subscribe.new(current_user.invoicing_profile.id, user_id)
|
||||
.pay_and_save(@subscription, coupon: coupon_params[:coupon_code],
|
||||
invoice: true,
|
||||
.pay_and_save(@subscription, payment_details: transaction[:details],
|
||||
schedule: params[:subscription][:payment_schedule],
|
||||
payment_method: params[:subscription][:payment_method])
|
||||
|
||||
@ -65,7 +64,7 @@ class API::SubscriptionsController < API::ApiController
|
||||
# Subtract wallet amount from total
|
||||
total = price_details[:total]
|
||||
wallet_debit = get_wallet_debit(user, total)
|
||||
total - wallet_debit
|
||||
{ amount: total - wallet_debit, details: price_details }
|
||||
end
|
||||
|
||||
def get_wallet_debit(user, total_amount)
|
||||
|
@ -19,6 +19,13 @@ client.interceptors.response.use(function (response) {
|
||||
});
|
||||
|
||||
function extractHumanReadableMessage(error: any): string {
|
||||
if (error.match(/^<!DOCTYPE html>/)) {
|
||||
// parse ruby error pages
|
||||
const parser = new DOMParser();
|
||||
const htmlDoc = parser.parseFromString(error, 'text/html');
|
||||
return htmlDoc.querySelector('h2').textContent;
|
||||
}
|
||||
|
||||
if (typeof error === 'string') return error;
|
||||
|
||||
let message = '';
|
||||
|
@ -761,7 +761,6 @@ Application.Controllers.controller('EditMemberController', ['$scope', '$state',
|
||||
size: 'lg',
|
||||
controller: ['$scope', '$uibModalInstance', 'Subscription', function ($scope, $uibModalInstance, Subscription) {
|
||||
$scope.new_expired_at = angular.copy(subscription.expired_at);
|
||||
$scope.scheduled = subscription.scheduled;
|
||||
$scope.free = free;
|
||||
$scope.datePicker = {
|
||||
opened: false,
|
||||
|
@ -716,9 +716,14 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
|
||||
* Open a modal window that allows the user to process a credit card payment for his current shopping cart.
|
||||
*/
|
||||
const payByStripe = function (reservation) {
|
||||
$scope.toggleStripeModal(() => {
|
||||
$scope.stripe.cartItems = mkCartItems(reservation, 'stripe');
|
||||
});
|
||||
// check that the online payment is enabled
|
||||
if ($scope.settings.online_payment_module !== 'true') {
|
||||
growl.error(_t('app.shared.cart.online_payment_disabled'));
|
||||
} else {
|
||||
$scope.toggleStripeModal(() => {
|
||||
$scope.stripe.cartItems = mkCartItems(reservation, 'stripe');
|
||||
});
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Open a modal window that allows the user to process a local payment for his current shopping cart (admin only).
|
||||
@ -751,10 +756,13 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
|
||||
},
|
||||
user () {
|
||||
return $scope.user;
|
||||
},
|
||||
settings () {
|
||||
return $scope.settings;
|
||||
}
|
||||
},
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'Reservation', 'Subscription', 'wallet', 'helpers', '$filter', 'coupon', 'selectedPlan', 'schedule', 'cartItems', 'user',
|
||||
function ($scope, $uibModalInstance, $state, reservation, price, Auth, Reservation, Subscription, wallet, helpers, $filter, coupon, selectedPlan, schedule, cartItems, user) {
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'Reservation', 'Subscription', 'wallet', 'helpers', '$filter', 'coupon', 'selectedPlan', 'schedule', 'cartItems', 'user', 'settings',
|
||||
function ($scope, $uibModalInstance, $state, reservation, price, Auth, Reservation, Subscription, wallet, helpers, $filter, coupon, selectedPlan, schedule, cartItems, user, settings) {
|
||||
// user wallet amount
|
||||
$scope.wallet = wallet;
|
||||
|
||||
@ -797,7 +805,12 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
|
||||
*/
|
||||
$scope.ok = function () {
|
||||
if ($scope.schedule && $scope.method.payment_method === 'stripe') {
|
||||
return $scope.toggleStripeModal();
|
||||
// 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.toggleStripeModal();
|
||||
}
|
||||
}
|
||||
$scope.attempting = true;
|
||||
// save subscription (if there's only a subscription selected)
|
||||
@ -927,11 +940,7 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
|
||||
const amountToPay = helpers.getAmountToPay($scope.amountTotal, wallet.amount);
|
||||
if ((AuthService.isAuthorized(['member']) && amountToPay > 0) ||
|
||||
(AuthService.isAuthorized('manager') && $scope.user.id === $rootScope.currentUser.id && amountToPay > 0)) {
|
||||
if ($scope.settings.online_payment_module !== 'true') {
|
||||
growl.error(_t('app.shared.cart.online_payment_disabled'));
|
||||
} else {
|
||||
return payByStripe(reservation);
|
||||
}
|
||||
return payByStripe(reservation);
|
||||
} else {
|
||||
if (AuthService.isAuthorized(['admin']) ||
|
||||
(AuthService.isAuthorized('manager') && $scope.user.id !== $rootScope.currentUser.id) ||
|
||||
|
@ -10,6 +10,7 @@
|
||||
<div ng-hide="free">
|
||||
<p translate>{{ 'app.admin.members_edit.you_intentionally_decide_to_extend_the_user_s_subscription_by_charging_him_again_for_his_current_subscription' }}</p>
|
||||
<p translate>{{ 'app.admin.members_edit.credits_will_be_reset' }}</p>
|
||||
<p translate>{{ 'app.admin.members_edit.payment_scheduled' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<form role="form" name="subscriptionForm" novalidate>
|
||||
|
@ -20,8 +20,7 @@ class Subscription < ApplicationRecord
|
||||
after_save :notify_partner_subscribed_plan, if: :of_partner_plan?
|
||||
|
||||
##
|
||||
# Set the inner properties of the subscription, init the user's credits and save the subscription
|
||||
# into the DB
|
||||
# Set the inner properties of the subscription, init the user's credits and save the subscription into the DB
|
||||
# @return {boolean} true, if the operation succeeded
|
||||
##
|
||||
def init_save
|
||||
@ -34,86 +33,6 @@ class Subscription < ApplicationRecord
|
||||
true
|
||||
end
|
||||
|
||||
# TODO, remove this method, refactor like services/Reservations::Reserve
|
||||
# @param invoice if true then only the subscription is payed, without reservation
|
||||
# if false then the subscription is payed with reservation
|
||||
# @param payment_method is only used for schedules
|
||||
def save_with_payment(operator_profile_id, invoice: true, coupon_code: nil, payment_intent_id: nil, schedule: nil, payment_method: nil)
|
||||
return false unless valid?
|
||||
|
||||
set_expiration_date
|
||||
return false unless save
|
||||
|
||||
UsersCredits::Manager.new(user: user).reset_credits
|
||||
if invoice
|
||||
@wallet_amount_debit = get_wallet_amount_debit
|
||||
|
||||
# debit wallet
|
||||
wallet_transaction = debit_user_wallet
|
||||
|
||||
payment = if schedule
|
||||
generate_schedule(operator_profile_id, payment_method, coupon_code)
|
||||
else
|
||||
generate_invoice(operator_profile_id, coupon_code, payment_intent_id)
|
||||
end
|
||||
|
||||
if wallet_transaction
|
||||
payment.wallet_amount = @wallet_amount_debit
|
||||
payment.wallet_transaction_id = wallet_transaction.id
|
||||
end
|
||||
payment.save
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def generate_schedule(operator_profile_id, payment_method, coupon_code = nil)
|
||||
operator = InvoicingProfile.find(operator_profile_id)&.user
|
||||
coupon = Coupon.find_by(code: coupon_code) unless coupon_code.nil?
|
||||
|
||||
PaymentScheduleService.new.create(
|
||||
self,
|
||||
plan.amount,
|
||||
coupon: coupon,
|
||||
operator: operator,
|
||||
payment_method: payment_method,
|
||||
user: user
|
||||
)
|
||||
end
|
||||
|
||||
def generate_invoice(operator_profile_id, coupon_code = nil, payment_intent_id = nil)
|
||||
coupon_id = nil
|
||||
total = plan.amount
|
||||
operator = InvoicingProfile.find(operator_profile_id)&.user
|
||||
method = operator&.admin? || (operator&.manager? && operator != user) ? nil : 'stripe'
|
||||
|
||||
unless coupon_code.nil?
|
||||
@coupon = Coupon.find_by(code: coupon_code)
|
||||
|
||||
unless @coupon.nil?
|
||||
total = CouponService.new.apply(plan.amount, @coupon, user.id)
|
||||
coupon_id = @coupon.id
|
||||
end
|
||||
end
|
||||
|
||||
invoice = Invoice.new(
|
||||
invoiced_id: id,
|
||||
invoiced_type: 'Subscription',
|
||||
invoicing_profile: user.invoicing_profile,
|
||||
statistic_profile: user.statistic_profile,
|
||||
total: total,
|
||||
coupon_id: coupon_id,
|
||||
operator_profile_id: operator_profile_id,
|
||||
stp_payment_intent_id: payment_intent_id,
|
||||
payment_method: method
|
||||
)
|
||||
invoice.invoice_items.push InvoiceItem.new(
|
||||
amount: plan.amount,
|
||||
description: plan.name,
|
||||
subscription_id: id
|
||||
)
|
||||
invoice
|
||||
end
|
||||
|
||||
def generate_and_save_invoice(operator_profile_id)
|
||||
generate_invoice(operator_profile_id).save
|
||||
end
|
||||
|
@ -29,11 +29,13 @@ class Reservations::Reserve
|
||||
generate_invoice(reservation, operator_profile_id, payment_details, payment_intent_id)
|
||||
end
|
||||
payment.save
|
||||
debit_user_wallet(payment, user, reservation)
|
||||
WalletService.debit_user_wallet(payment, user, reservation)
|
||||
reservation.post_save
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
##
|
||||
# Generate the invoice for the given reservation+subscription
|
||||
##
|
||||
@ -64,33 +66,4 @@ class Reservations::Reserve
|
||||
)
|
||||
end
|
||||
|
||||
##
|
||||
# Compute the amount decreased from the user's wallet, if applicable
|
||||
# @param payment {Invoice|PaymentSchedule}
|
||||
# @param user {User} the customer
|
||||
# @param coupon {Coupon|String} Coupon object or code
|
||||
##
|
||||
def wallet_amount_debit(payment, user, coupon = nil)
|
||||
total = payment.total
|
||||
total = CouponService.new.apply(total, coupon, user.id) if coupon
|
||||
|
||||
wallet_amount = (user.wallet.amount * 100).to_i
|
||||
|
||||
wallet_amount >= total ? total : wallet_amount
|
||||
end
|
||||
|
||||
##
|
||||
# Subtract the amount of the current reservation from the customer's wallet
|
||||
##
|
||||
def debit_user_wallet(payment, user, reservation)
|
||||
wallet_amount = wallet_amount_debit(payment, user)
|
||||
return unless wallet_amount.present? && wallet_amount != 0
|
||||
|
||||
amount = wallet_amount / 100.0
|
||||
wallet_transaction = WalletService.new(user: user, wallet: user.wallet).debit(amount, reservation)
|
||||
# wallet debit success
|
||||
raise DebitWalletError unless wallet_transaction
|
||||
|
||||
payment.set_wallet_transaction(wallet_amount, wallet_transaction.id)
|
||||
end
|
||||
end
|
||||
|
@ -11,22 +11,31 @@ class Subscriptions::Subscribe
|
||||
|
||||
##
|
||||
# @param subscription {Subscription}
|
||||
# @param coupon {String} coupon code
|
||||
# @param invoice {Boolean}
|
||||
# @param payment_details {Hash} as generated by Price.compute
|
||||
# @param payment_intent_id {String} from stripe
|
||||
# @param schedule {Boolean}
|
||||
# @param payment_method {String} only for schedules
|
||||
##
|
||||
def pay_and_save(subscription, coupon: nil, invoice: false, payment_intent_id: nil, schedule: false, payment_method: nil)
|
||||
def pay_and_save(subscription, payment_details: nil, payment_intent_id: nil, schedule: false, payment_method: nil)
|
||||
return false if user_id.nil?
|
||||
|
||||
subscription.statistic_profile_id = StatisticProfile.find_by(user_id: user_id).id
|
||||
subscription.save_with_payment(operator_profile_id,
|
||||
invoice: invoice,
|
||||
coupon_code: coupon,
|
||||
payment_intent_id: payment_intent_id,
|
||||
schedule: schedule,
|
||||
payment_method: payment_method)
|
||||
subscription.init_save
|
||||
user = User.find(user_id)
|
||||
|
||||
payment = if schedule
|
||||
generate_schedule(subscription: subscription,
|
||||
total: payment_details[:before_coupon],
|
||||
operator_profile_id: operator_profile_id,
|
||||
user: user,
|
||||
payment_method: payment_method,
|
||||
coupon_code: payment_details[:coupon])
|
||||
else
|
||||
generate_invoice(subscription, operator_profile_id, payment_details, payment_intent_id)
|
||||
end
|
||||
payment.save
|
||||
WalletService.debit_user_wallet(payment, user, subscription)
|
||||
true
|
||||
end
|
||||
|
||||
def extend_subscription(subscription, new_expiration_date, free_days)
|
||||
@ -38,10 +47,53 @@ class Subscriptions::Subscribe
|
||||
expiration_date: new_expiration_date
|
||||
)
|
||||
if new_sub.save
|
||||
new_sub.user.generate_subscription_invoice(operator_profile_id)
|
||||
schedule = subscription.payment_schedule
|
||||
details = Price.compute(true, new_sub.user, nil, [], plan_id: subscription.plan_id)
|
||||
payment = if schedule
|
||||
generate_schedule(subscription: new_sub,
|
||||
total: details[:before_coupon],
|
||||
operator_profile_id: operator_profile_id,
|
||||
user: new_sub.user,
|
||||
payment_method: schedule.payment_method)
|
||||
else
|
||||
generate_invoice(subscription, operator_profile_id, details)
|
||||
end
|
||||
payment.save
|
||||
UsersCredits::Manager.new(user: new_sub.user).reset_credits
|
||||
return new_sub
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
##
|
||||
# Generate the invoice for the given subscription
|
||||
##
|
||||
def generate_schedule(subscription: nil, total: nil, operator_profile_id: nil, user: nil, payment_method: nil, coupon_code: nil)
|
||||
operator = InvoicingProfile.find(operator_profile_id)&.user
|
||||
coupon = Coupon.find_by(code: coupon_code) unless coupon_code.nil?
|
||||
|
||||
PaymentScheduleService.new.create(
|
||||
subscription,
|
||||
total,
|
||||
coupon: coupon,
|
||||
operator: operator,
|
||||
payment_method: payment_method,
|
||||
user: user
|
||||
)
|
||||
end
|
||||
|
||||
##
|
||||
# Generate the invoice for the given subscription
|
||||
##
|
||||
def generate_invoice(subscription, operator_profile_id, payment_details, payment_intent_id = nil)
|
||||
InvoicesService.create(
|
||||
payment_details,
|
||||
operator_profile_id,
|
||||
subscription: subscription,
|
||||
payment_intent_id: payment_intent_id
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -72,4 +72,34 @@ class WalletService
|
||||
ii.invoice = avoir
|
||||
ii.save!
|
||||
end
|
||||
|
||||
##
|
||||
# Compute the amount decreased from the user's wallet, if applicable
|
||||
# @param payment {Invoice|PaymentSchedule}
|
||||
# @param user {User} the customer
|
||||
# @param coupon {Coupon|String} Coupon object or code
|
||||
##
|
||||
def self.wallet_amount_debit(payment, user, coupon = nil)
|
||||
total = payment.total
|
||||
total = CouponService.new.apply(total, coupon, user.id) if coupon
|
||||
|
||||
wallet_amount = (user.wallet.amount * 100).to_i
|
||||
|
||||
wallet_amount >= total ? total : wallet_amount
|
||||
end
|
||||
|
||||
##
|
||||
# Subtract the amount of the transactable item (Subscription|Reservation) from the customer's wallet
|
||||
##
|
||||
def self.debit_user_wallet(payment, user, transactable)
|
||||
wallet_amount = WalletService.wallet_amount_debit(payment, user)
|
||||
return unless wallet_amount.present? && wallet_amount != 0
|
||||
|
||||
amount = wallet_amount / 100.0
|
||||
wallet_transaction = WalletService.new(user: user, wallet: user.wallet).debit(amount, transactable)
|
||||
# wallet debit success
|
||||
raise DebitWalletError unless wallet_transaction
|
||||
|
||||
payment.set_wallet_transaction(wallet_amount, wallet_transaction.id)
|
||||
end
|
||||
end
|
||||
|
@ -815,6 +815,7 @@ en:
|
||||
credits_will_remain_unchanged: "The balance of free credits (training / machines / spaces) of the user will remain unchanged."
|
||||
you_intentionally_decide_to_extend_the_user_s_subscription_by_charging_him_again_for_his_current_subscription: "You intentionally decide to extend the user's subscription by charging him again for his current subscription."
|
||||
credits_will_be_reset: "The balance of free credits (training / machines / spaces) of the user will be reset, unused credits will be lost."
|
||||
payment_scheduled: "If the previous subscription was charged through a payment schedule, this one will be charged the same way."
|
||||
until_expiration_date: "Until (expiration date):"
|
||||
you_successfully_changed_the_expiration_date_of_the_user_s_subscription: "You successfully changed the expiration date of the user's subscription"
|
||||
a_problem_occurred_while_saving_the_date: "A problem occurred while saving the date."
|
||||
|
@ -815,6 +815,7 @@ fr:
|
||||
credits_will_remain_unchanged: "Le solde de crédits gratuits (formations/machines/espaces) de l'utilisateur restera inchangé."
|
||||
you_intentionally_decide_to_extend_the_user_s_subscription_by_charging_him_again_for_his_current_subscription: "Vous décidez délibérément d'étendre l'abonnement de l'utilisateur en lui faisant repayer le prix de l'abonnement qu'il possède actuellement."
|
||||
credits_will_be_reset: "Le solde de crédits gratuits (formations/machines/espaces) de l'utilisateur sera remis à zéro, ses crédits non utilisés seront perdu."
|
||||
payment_scheduled: "Si l'abonnement précédent a été facturé via un échéancier de paiement mensualisé, celui-ci sera facturé de la même façon."
|
||||
until_expiration_date: "Jusqu'à (date d'expiration) :"
|
||||
you_successfully_changed_the_expiration_date_of_the_user_s_subscription: "Vous avez bien modifié la date d'expiration de l'abonnement de l'utilisateur"
|
||||
a_problem_occurred_while_saving_the_date: "Il y a eu un problème lors de l'enregistrement de la date."
|
||||
|
Loading…
x
Reference in New Issue
Block a user