1
0
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:
Sylvain 2020-12-21 16:12:34 +01:00
parent e0ac9d1ac3
commit 609d19e5d1
12 changed files with 134 additions and 144 deletions

View File

@ -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')

View File

@ -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)

View File

@ -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 = '';

View File

@ -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,

View File

@ -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) ||

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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."

View File

@ -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."