mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2024-12-01 12:24:28 +01:00
use Price.compute to generate invoice items
This commit is contained in:
parent
19de8ca319
commit
be23cf27c3
@ -51,10 +51,10 @@ class API::PaymentsController < API::ApiController
|
||||
|
||||
private
|
||||
|
||||
def on_reservation_success(intent)
|
||||
def on_reservation_success(intent, details)
|
||||
@reservation = Reservation.new(reservation_params)
|
||||
is_reserve = Reservations::Reserve.new(current_user.id, current_user.invoicing_profile.id)
|
||||
.pay_and_save(@reservation, coupon: coupon_params[:coupon_code], payment_intent_id: intent.id)
|
||||
.pay_and_save(@reservation, payment_details: details, payment_intent_id: intent.id)
|
||||
Stripe::PaymentIntent.update(
|
||||
intent.id,
|
||||
description: "Invoice reference: #{@reservation.invoice.reference}"
|
||||
|
@ -29,13 +29,13 @@ class API::ReservationsController < API::ApiController
|
||||
# Managers can create reservations for other users
|
||||
def create
|
||||
user_id = current_user.admin? || current_user.manager? ? params[:reservation][:user_id] : current_user.id
|
||||
amount = transaction_amount(current_user.admin? || (current_user.manager? && current_user.id != user_id), user_id)
|
||||
price = transaction_amount(current_user.admin? || (current_user.manager? && current_user.id != user_id), user_id)
|
||||
|
||||
authorize ReservationContext.new(Reservation, amount, user_id)
|
||||
authorize ReservationContext.new(Reservation, price[:amount], user_id)
|
||||
|
||||
@reservation = Reservation.new(reservation_params)
|
||||
is_reserve = Reservations::Reserve.new(user_id, current_user.invoicing_profile.id)
|
||||
.pay_and_save(@reservation, coupon: coupon_params[:coupon_code])
|
||||
.pay_and_save(@reservation, payment_details: price[:price_details])
|
||||
|
||||
if is_reserve
|
||||
SubscriptionExtensionAfterReservation.new(@reservation).extend_subscription_if_eligible
|
||||
@ -73,7 +73,8 @@ class API::ReservationsController < API::ApiController
|
||||
# Subtract wallet amount from total
|
||||
total = price_details[:total]
|
||||
wallet_debit = get_wallet_debit(user, total)
|
||||
total - wallet_debit
|
||||
|
||||
{ price_details: price_details, amount: (total - wallet_debit) }
|
||||
end
|
||||
|
||||
def get_wallet_debit(user, total_amount)
|
||||
|
@ -138,10 +138,12 @@ class Price < ApplicationRecord
|
||||
|
||||
# === apply Coupon if any ===
|
||||
_amount_no_coupon = total_amount
|
||||
total_amount = CouponService.new.apply(total_amount, coupon_code)
|
||||
cs = CouponService.new
|
||||
cp = cs.validate(coupon_code, user.id)
|
||||
total_amount = cs.apply(total_amount, cp)
|
||||
|
||||
# return result
|
||||
{ elements: all_elements, total: total_amount.to_i, before_coupon: _amount_no_coupon.to_i }
|
||||
{ elements: all_elements, total: total_amount.to_i, before_coupon: _amount_no_coupon.to_i, coupon: cp }
|
||||
end
|
||||
|
||||
|
||||
|
@ -33,75 +33,17 @@ class Reservation < ApplicationRecord
|
||||
|
||||
##
|
||||
# Generate an array of {Stripe::InvoiceItem} with the elements in the current reservation, price included.
|
||||
# The training/machine price is depending of the member's group, subscription and credits already used
|
||||
# @param on_site {Boolean} true if an admin triggered the call
|
||||
# @param coupon_code {String} pass a valid code to apply a coupon
|
||||
# @param payment_details {Hash} as generated by Price.compute
|
||||
##
|
||||
def generate_invoice_items(on_site = false, coupon_code = nil)
|
||||
# prepare the plan
|
||||
plan = if user.subscribed_plan
|
||||
user.subscribed_plan
|
||||
elsif plan_id
|
||||
Plan.find(plan_id)
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
def generate_invoice_items(payment_details = nil)
|
||||
# check that none of the reserved availabilities was locked
|
||||
slots.each do |slot|
|
||||
raise LockedError if slot.availability.lock
|
||||
end
|
||||
|
||||
case reservable
|
||||
|
||||
# === Machine reservation ===
|
||||
when Machine
|
||||
base_amount = reservable.prices.find_by(group_id: user.group_id, plan_id: plan.try(:id)).amount
|
||||
users_credits_manager = UsersCredits::Manager.new(reservation: self, plan: plan)
|
||||
|
||||
slots.each_with_index do |slot, index|
|
||||
description = reservable.name +
|
||||
" #{I18n.l slot.start_at, format: :long} - #{I18n.l slot.end_at, format: :hour_minute}"
|
||||
|
||||
ii_amount = base_amount # ii_amount default to base_amount
|
||||
|
||||
if users_credits_manager.will_use_credits?
|
||||
ii_amount = index < users_credits_manager.free_hours_count ? 0 : base_amount
|
||||
end
|
||||
|
||||
ii_amount = 0 if slot.offered && on_site # if it's a local payment and slot is offered free
|
||||
|
||||
invoice.invoice_items.push InvoiceItem.new(
|
||||
amount: ii_amount,
|
||||
description: description
|
||||
)
|
||||
end
|
||||
|
||||
# === Training reservation ===
|
||||
when Training
|
||||
base_amount = reservable.amount_by_group(user.group_id).amount
|
||||
|
||||
# be careful, variable plan can be the user's plan OR the plan user is currently purchasing
|
||||
users_credits_manager = UsersCredits::Manager.new(reservation: self, plan: plan)
|
||||
base_amount = 0 if users_credits_manager.will_use_credits?
|
||||
|
||||
slots.each do |slot|
|
||||
description = reservable.name +
|
||||
" #{I18n.l slot.start_at, format: :long} - #{I18n.l slot.end_at, format: :hour_minute}"
|
||||
ii_amount = base_amount
|
||||
ii_amount = 0 if slot.offered && on_site
|
||||
invoice.invoice_items.push InvoiceItem.new(
|
||||
amount: ii_amount,
|
||||
description: description
|
||||
)
|
||||
end
|
||||
|
||||
# === Event reservation ===
|
||||
when Event
|
||||
amount = reservable.amount * nb_reserve_places
|
||||
tickets.each do |ticket|
|
||||
amount += ticket.booked * ticket.event_price_category.amount
|
||||
end
|
||||
slots.each do |slot|
|
||||
description = "#{reservable.name}\n"
|
||||
description += if slot.start_at.to_date != slot.end_at.to_date
|
||||
@ -115,69 +57,32 @@ class Reservation < ApplicationRecord
|
||||
"#{I18n.l slot.start_at.to_date, format: :long} #{I18n.l slot.start_at, format: :hour_minute}" \
|
||||
" - #{I18n.l slot.end_at, format: :hour_minute}"
|
||||
end
|
||||
ii_amount = amount
|
||||
ii_amount = 0 if slot.offered && on_site
|
||||
|
||||
price_slot = payment_details[:elements][:slots].detect { |p_slot| p_slot[:start_at].to_time.in_time_zone == slot[:start_at] }
|
||||
invoice.invoice_items.push InvoiceItem.new(
|
||||
amount: ii_amount,
|
||||
amount: price_slot[:price],
|
||||
description: description
|
||||
)
|
||||
end
|
||||
|
||||
# === Space reservation ===
|
||||
when Space
|
||||
base_amount = reservable.prices.find_by(group_id: user.group_id, plan_id: plan.try(:id)).amount
|
||||
users_credits_manager = UsersCredits::Manager.new(reservation: self, plan: plan)
|
||||
|
||||
slots.each_with_index do |slot, index|
|
||||
description = reservable.name + " #{I18n.l slot.start_at, format: :long} - #{I18n.l slot.end_at, format: :hour_minute}"
|
||||
|
||||
ii_amount = base_amount # ii_amount default to base_amount
|
||||
|
||||
if users_credits_manager.will_use_credits?
|
||||
ii_amount = index < users_credits_manager.free_hours_count ? 0 : base_amount
|
||||
end
|
||||
|
||||
ii_amount = 0 if slot.offered && on_site # if it's a local payment and slot is offered free
|
||||
|
||||
invoice.invoice_items.push InvoiceItem.new(
|
||||
amount: ii_amount,
|
||||
description: description
|
||||
)
|
||||
end
|
||||
|
||||
# === Unknown reservation type ===
|
||||
# === Space|Machine|Training reservation ===
|
||||
else
|
||||
raise NotImplementedError
|
||||
slots.each do |slot|
|
||||
description = reservable.name +
|
||||
" #{I18n.l slot.start_at, format: :long} - #{I18n.l slot.end_at, format: :hour_minute}"
|
||||
|
||||
price_slot = payment_details[:elements][:slots].detect { |p_slot| p_slot[:start_at].to_time.in_time_zone == slot[:start_at] }
|
||||
invoice.invoice_items.push InvoiceItem.new(
|
||||
amount: price_slot[:price],
|
||||
description: description
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# === Coupon ===
|
||||
unless coupon_code.nil?
|
||||
@coupon = Coupon.find_by(code: coupon_code)
|
||||
raise InvalidCouponError if @coupon.nil? || @coupon.status(user.id) != 'active'
|
||||
|
||||
total = cart_total
|
||||
|
||||
discount = if @coupon.type == 'percent_off'
|
||||
(total * @coupon.percent_off / 100).to_i
|
||||
elsif @coupon.type == 'amount_off'
|
||||
@coupon.amount_off
|
||||
else
|
||||
raise InvalidCouponError
|
||||
end
|
||||
end
|
||||
@coupon = payment_details[:coupon]
|
||||
|
||||
# === Wallet ===
|
||||
@wallet_amount_debit = wallet_amount_debit
|
||||
# if @wallet_amount_debit != 0 && !on_site
|
||||
# invoice_items << Stripe::InvoiceItem.create(
|
||||
# customer: user.stp_customer_id,
|
||||
# amount: -@wallet_amount_debit.to_i,
|
||||
# currency: Rails.application.secrets.stripe_currency,
|
||||
# description: "wallet -#{@wallet_amount_debit / 100.0}"
|
||||
# )
|
||||
# end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
# check reservation amount total and strip invoice total to pay is equal
|
||||
@ -211,7 +116,7 @@ class Reservation < ApplicationRecord
|
||||
pending_invoice_items.each(&:delete)
|
||||
end
|
||||
|
||||
def save_with_payment(operator_profile_id, coupon_code = nil, payment_intent_id = nil)
|
||||
def save_with_payment(operator_profile_id, payment_details, payment_intent_id = nil)
|
||||
operator = InvoicingProfile.find(operator_profile_id)&.user
|
||||
method = operator&.admin? || (operator&.manager? && operator != user) ? nil : 'stripe'
|
||||
|
||||
@ -222,7 +127,7 @@ class Reservation < ApplicationRecord
|
||||
stp_payment_intent_id: payment_intent_id,
|
||||
payment_method: method
|
||||
)
|
||||
generate_invoice_items(true, coupon_code)
|
||||
generate_invoice_items(payment_details)
|
||||
|
||||
return false unless valid?
|
||||
|
||||
@ -231,18 +136,18 @@ class Reservation < ApplicationRecord
|
||||
subscription.attributes = { plan_id: plan_id, statistic_profile_id: statistic_profile_id, expiration_date: nil }
|
||||
if subscription.save_with_payment(operator_profile_id, false)
|
||||
invoice.invoice_items.push InvoiceItem.new(
|
||||
amount: subscription.plan.amount,
|
||||
amount: payment_details[:elements][:plan],
|
||||
description: subscription.plan.name,
|
||||
subscription_id: subscription.id
|
||||
)
|
||||
set_total_and_coupon(coupon_code)
|
||||
set_total_and_coupon(payment_details[:coupon])
|
||||
save!
|
||||
else
|
||||
errors[:card] << subscription.errors[:card].join
|
||||
return false
|
||||
end
|
||||
else
|
||||
set_total_and_coupon(coupon_code)
|
||||
set_total_and_coupon(payment_details[:coupon])
|
||||
save!
|
||||
end
|
||||
|
||||
@ -355,16 +260,13 @@ class Reservation < ApplicationRecord
|
||||
##
|
||||
# Set the total price to the reservation's invoice, summing its whole items.
|
||||
# Additionally a coupon may be applied to this invoice to make a discount on the total price
|
||||
# @param [coupon_code] {String} optional coupon code to apply to the invoice
|
||||
# @param [coupon] {Coupon} optional coupon to apply to the invoice
|
||||
##
|
||||
def set_total_and_coupon(coupon_code = nil)
|
||||
def set_total_and_coupon(coupon = nil)
|
||||
total = invoice.invoice_items.map(&:amount).map(&:to_i).reduce(:+)
|
||||
|
||||
unless coupon_code.nil?
|
||||
cp = Coupon.find_by(code: coupon_code)
|
||||
raise InvalidCouponError unless !cp.nil? && cp.status(user.id) == 'active'
|
||||
|
||||
total = CouponService.new.apply(total, cp, user.id)
|
||||
unless coupon.nil?
|
||||
total = CouponService.new.apply(total, coupon, user.id)
|
||||
invoice.coupon_id = cp.id
|
||||
end
|
||||
|
||||
|
@ -36,6 +36,21 @@ class CouponService
|
||||
price
|
||||
end
|
||||
|
||||
##
|
||||
# Find the coupon associated with the given code and check it is valid for the given user
|
||||
# @param code {String} the literal code of the coupon
|
||||
# @param user_id {Number} identifier of the user who is applying the coupon
|
||||
# @return {Coupon}
|
||||
##
|
||||
def validate(code, user_id)
|
||||
return nil unless code && user_id
|
||||
|
||||
coupon = Coupon.find_by(code: code)
|
||||
raise InvalidCouponError if coupon.nil? || coupon.status(user_id) != 'active'
|
||||
|
||||
coupon
|
||||
end
|
||||
|
||||
##
|
||||
# Ventilate the discount of the provided coupon over the given amount proportionately to the invoice's total
|
||||
# @param total {Number} total amount of the invoice expressed in monetary units
|
||||
|
@ -9,8 +9,8 @@ class Reservations::Reserve
|
||||
@operator_profile_id = operator_profile_id
|
||||
end
|
||||
|
||||
def pay_and_save(reservation, coupon: nil, payment_intent_id: nil)
|
||||
def pay_and_save(reservation, payment_details: nil, payment_intent_id: nil)
|
||||
reservation.statistic_profile_id = StatisticProfile.find_by(user_id: user_id).id
|
||||
reservation.save_with_payment(operator_profile_id, coupon, payment_intent_id)
|
||||
reservation.save_with_payment(operator_profile_id, payment_details, payment_intent_id)
|
||||
end
|
||||
end
|
||||
|
@ -1,8 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'forwardable'
|
||||
|
||||
module UsersCredits
|
||||
class AlreadyUpdatedError < StandardError; end
|
||||
|
||||
# You must use UsersCredits::Manager to consume the credits of a user or to reset them
|
||||
class Manager
|
||||
extend Forwardable
|
||||
attr_reader :manager
|
||||
@ -30,6 +33,8 @@ module UsersCredits
|
||||
def_delegators :@manager, :will_use_credits?, :free_hours_count, :update_credits, :reset_credits
|
||||
end
|
||||
|
||||
# The classes contained in UsersCredits::Managers are used by UsersCredits::Manager (no s) to handle the credits for
|
||||
# the various kinds of reservations and for the user
|
||||
module Managers
|
||||
# that class is responsible for resetting users_credits of a user
|
||||
class User
|
||||
@ -44,6 +49,7 @@ module UsersCredits
|
||||
end
|
||||
end
|
||||
|
||||
# Parent class of all reservations managers
|
||||
class Reservation
|
||||
attr_reader :reservation
|
||||
|
||||
@ -119,7 +125,7 @@ module UsersCredits
|
||||
return false, free_hours_count, machine_credit
|
||||
end
|
||||
end
|
||||
return false, 0
|
||||
[false, 0]
|
||||
end
|
||||
end
|
||||
|
||||
@ -149,10 +155,11 @@ module UsersCredits
|
||||
return true, training_credit
|
||||
end
|
||||
end
|
||||
return false, nil
|
||||
[false, nil]
|
||||
end
|
||||
end
|
||||
|
||||
# same as class Machine but for Event reservation
|
||||
class Event < Reservation
|
||||
def will_use_credits?
|
||||
false
|
||||
@ -161,6 +168,7 @@ module UsersCredits
|
||||
def update_credits; end
|
||||
end
|
||||
|
||||
# same as class Machine but for Space reservation
|
||||
class Space < Reservation
|
||||
# to known if a credit will be used in the context of the given reservation
|
||||
def will_use_credits?
|
||||
@ -206,7 +214,7 @@ module UsersCredits
|
||||
return false, free_hours_count, space_credit
|
||||
end
|
||||
end
|
||||
return false, 0
|
||||
[false, 0]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user