mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-29 18:52:22 +01:00
refactor to use the new price computation system based on ShoppingCart
This commit is contained in:
parent
e456ddc7c9
commit
94cbcd3236
@ -18,40 +18,9 @@ class API::PaymentsController < API::ApiController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def card_amount
|
def card_amount
|
||||||
if params[:cart_items][:reservation]
|
cs = CartService.new(current_user)
|
||||||
reservable = cart_items_params[:reservable_type].constantize.find(cart_items_params[:reservable_id])
|
cart = cs.from_hash(params[:cart_items])
|
||||||
plan_id = cart_items_params[:plan_id]
|
price_details = cart.total
|
||||||
slots = cart_items_params[:slots_attributes] || []
|
|
||||||
nb_places = cart_items_params[:nb_reserve_places]
|
|
||||||
tickets = cart_items_params[:tickets_attributes]
|
|
||||||
user_id = if current_user.admin? || current_user.manager?
|
|
||||||
params[:cart_items][:reservation][:user_id]
|
|
||||||
else
|
|
||||||
current_user.id
|
|
||||||
end
|
|
||||||
else
|
|
||||||
raise NotImplementedError unless params[:cart_items][:subscription]
|
|
||||||
|
|
||||||
reservable = nil
|
|
||||||
plan_id = subscription_params[:plan_id]
|
|
||||||
slots = []
|
|
||||||
nb_places = nil
|
|
||||||
tickets = nil
|
|
||||||
user_id = if current_user.admin? || current_user.manager?
|
|
||||||
params[:cart_items][:subscription][:user_id]
|
|
||||||
else
|
|
||||||
current_user.id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
price_details = Price.compute(false,
|
|
||||||
User.find(user_id),
|
|
||||||
reservable,
|
|
||||||
slots,
|
|
||||||
plan_id: plan_id,
|
|
||||||
nb_places: nb_places,
|
|
||||||
tickets: tickets,
|
|
||||||
coupon_code: coupon_params[:coupon_code])
|
|
||||||
|
|
||||||
# Subtract wallet amount from total
|
# Subtract wallet amount from total
|
||||||
total = price_details[:total]
|
total = price_details[:total]
|
||||||
|
@ -24,14 +24,14 @@ class API::PayzenController < API::PaymentsController
|
|||||||
client = PayZen::Charge.new
|
client = PayZen::Charge.new
|
||||||
@result = client.create_payment(amount: amount[:amount],
|
@result = client.create_payment(amount: amount[:amount],
|
||||||
order_id: @id,
|
order_id: @id,
|
||||||
customer: PayZen::Helper.generate_customer(params[:customer_id]))
|
customer: PayZen::Helper.generate_customer(params[:customer_id], params[:cart_items]))
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_token
|
def create_token
|
||||||
@id = PayZen::Helper.generate_ref(cart_items_params, params[:customer_id])
|
@id = PayZen::Helper.generate_ref(cart_items_params, params[:customer_id])
|
||||||
client = PayZen::Charge.new
|
client = PayZen::Charge.new
|
||||||
@result = client.create_token(order_id: @id,
|
@result = client.create_token(order_id: @id,
|
||||||
customer: PayZen::Helper.generate_customer(params[:customer_id]))
|
customer: PayZen::Helper.generate_customer(params[:customer_id], params[:cart_items]))
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_hash
|
def check_hash
|
||||||
|
@ -37,39 +37,9 @@ class API::PricesController < API::ApiController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def compute
|
def compute
|
||||||
price_parameters = if params[:reservation]
|
cs = CartService.new(current_user)
|
||||||
compute_reservation_price_params
|
cart = cs.from_hash(params)
|
||||||
elsif params[:subscription]
|
@amount = cart.total
|
||||||
compute_subscription_price_params
|
|
||||||
end
|
|
||||||
# user
|
|
||||||
user = User.find(price_parameters[:user_id])
|
|
||||||
# reservable
|
|
||||||
if [nil, ''].include?(price_parameters[:reservable_id]) && ['', nil].include?(price_parameters[:plan_id])
|
|
||||||
@amount = { elements: nil, total: 0, before_coupon: 0 }
|
|
||||||
else
|
|
||||||
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),
|
|
||||||
user,
|
|
||||||
reservable,
|
|
||||||
price_parameters[:slots_attributes] || [],
|
|
||||||
plan_id: price_parameters[:plan_id],
|
|
||||||
nb_places: price_parameters[:nb_reserve_places],
|
|
||||||
tickets: price_parameters[:tickets_attributes],
|
|
||||||
coupon_code: coupon_params[:coupon_code],
|
|
||||||
payment_schedule: price_parameters[:payment_schedule])
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
if @amount.nil?
|
|
||||||
render status: :unprocessable_entity
|
|
||||||
else
|
|
||||||
render status: :ok
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -64,14 +64,15 @@ class API::ReservationsController < API::ApiController
|
|||||||
|
|
||||||
def transaction_amount(is_admin, user_id)
|
def transaction_amount(is_admin, user_id)
|
||||||
user = User.find(user_id)
|
user = User.find(user_id)
|
||||||
price_details = Price.compute(is_admin,
|
cs = CartService.new(current_user)
|
||||||
user,
|
cart = cs.from_hash(customer_id: user_id,
|
||||||
reservation_params[:reservable_type].constantize.find(reservation_params[:reservable_id]),
|
subscription: {
|
||||||
reservation_params[:slots_attributes] || [],
|
plan_id: reservation_params[:plan_id]
|
||||||
plan_id: reservation_params[:plan_id],
|
},
|
||||||
nb_places: reservation_params[:nb_reserve_places],
|
reservation: reservation_params,
|
||||||
tickets: reservation_params[:tickets_attributes],
|
coupon_code: coupon_params[:coupon_code],
|
||||||
coupon_code: coupon_params[:coupon_code])
|
payment_schedule: !schedule.nil?)
|
||||||
|
price_details = cart.total
|
||||||
|
|
||||||
# Subtract wallet amount from total
|
# Subtract wallet amount from total
|
||||||
total = price_details[:total]
|
total = price_details[:total]
|
||||||
|
@ -14,7 +14,7 @@ class API::SubscriptionsController < API::ApiController
|
|||||||
# Managers can create subscriptions for other users
|
# Managers can create subscriptions for other users
|
||||||
def create
|
def create
|
||||||
user_id = current_user.admin? || current_user.manager? ? params[:subscription][:user_id] : current_user.id
|
user_id = current_user.admin? || current_user.manager? ? params[:subscription][:user_id] : current_user.id
|
||||||
transaction = transaction_amount(current_user.admin? || (current_user.manager? && current_user.id != user_id), user_id)
|
transaction = transaction_amount(user_id)
|
||||||
|
|
||||||
authorize SubscriptionContext.new(Subscription, transaction[:amount], user_id)
|
authorize SubscriptionContext.new(Subscription, transaction[:amount], user_id)
|
||||||
|
|
||||||
@ -50,16 +50,16 @@ class API::SubscriptionsController < API::ApiController
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def transaction_amount(is_admin, user_id)
|
def transaction_amount(user_id)
|
||||||
|
cs = CartService.new(current_user)
|
||||||
|
cart = cs.from_hash(customer_id: user_id,
|
||||||
|
subscription: {
|
||||||
|
plan_id: subscription_params[:plan_id]
|
||||||
|
},
|
||||||
|
coupon_code: coupon_params[:coupon_code],
|
||||||
|
payment_schedule: !schedule.nil?)
|
||||||
|
price_details = cart.total
|
||||||
user = User.find(user_id)
|
user = User.find(user_id)
|
||||||
price_details = Price.compute(is_admin,
|
|
||||||
user,
|
|
||||||
nil,
|
|
||||||
[],
|
|
||||||
plan_id: subscription_params[:plan_id],
|
|
||||||
nb_places: nil,
|
|
||||||
tickets: nil,
|
|
||||||
coupon_code: coupon_params[:coupon_code])
|
|
||||||
|
|
||||||
# Subtract wallet amount from total
|
# Subtract wallet amount from total
|
||||||
total = price_details[:total]
|
total = price_details[:total]
|
||||||
|
@ -10,4 +10,8 @@ class CartItem::BaseItem
|
|||||||
def price
|
def price
|
||||||
{ elements: {}, amount: 0 }
|
{ elements: {}, amount: 0 }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def name
|
||||||
|
''
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -33,4 +33,7 @@ class CartItem::EventReservation < CartItem::Reservation
|
|||||||
{ elements: elements, amount: amount }
|
{ elements: elements, amount: amount }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def name
|
||||||
|
@reservable.title
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -29,6 +29,10 @@ class CartItem::Reservation < CartItem::BaseItem
|
|||||||
{ elements: elements, amount: amount }
|
{ elements: elements, amount: amount }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def name
|
||||||
|
@reservable.name
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def credits
|
def credits
|
||||||
|
@ -14,4 +14,8 @@ class CartItem::Subscription < CartItem::BaseItem
|
|||||||
|
|
||||||
{ elements: elements, amount: amount }
|
{ elements: elements, amount: amount }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def name
|
||||||
|
@plan.name
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -12,8 +12,7 @@ class PaymentGatewayObject < ApplicationRecord
|
|||||||
belongs_to :payment_schedule_item, foreign_type: 'PaymentScheduleItem', foreign_key: 'item_id'
|
belongs_to :payment_schedule_item, foreign_type: 'PaymentScheduleItem', foreign_key: 'item_id'
|
||||||
|
|
||||||
def gateway_object
|
def gateway_object
|
||||||
item = Payment::ItemBuilder.build(gateway_object_type)
|
Payment::ItemBuilder.build(gateway_object_type, gateway_object_id)
|
||||||
item.retrieve(gateway_object_id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def gateway_object=(object)
|
def gateway_object=(object)
|
||||||
|
@ -48,8 +48,8 @@ class PaymentSchedule < PaymentDocument
|
|||||||
payment_schedule_items.order(due_date: :asc)
|
payment_schedule_items.order(due_date: :asc)
|
||||||
end
|
end
|
||||||
|
|
||||||
def gateway_object(klass)
|
def gateway_payment_mean
|
||||||
payment_gateway_objects.find_by(gateway_object_type: klass)
|
payment_gateway_objects.map(&:gateway_object).find(&:payment_mean?)
|
||||||
end
|
end
|
||||||
|
|
||||||
def user
|
def user
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
MINUTES_PER_HOUR = 60.0
|
|
||||||
SECONDS_PER_MINUTE = 60.0
|
|
||||||
|
|
||||||
# Store customized price for various items (Machine, Space), depending on the group and on the plan
|
# Store customized price for various items (Machine, Space), depending on the group and on the plan
|
||||||
# Also provides a static helper method to compute the price details of a shopping cart
|
|
||||||
class Price < ApplicationRecord
|
class Price < ApplicationRecord
|
||||||
belongs_to :group
|
belongs_to :group
|
||||||
belongs_to :plan
|
belongs_to :plan
|
||||||
@ -12,203 +8,4 @@ class Price < ApplicationRecord
|
|||||||
|
|
||||||
validates :priceable, :group_id, :amount, presence: true
|
validates :priceable, :group_id, :amount, presence: true
|
||||||
validates :priceable_id, uniqueness: { scope: %i[priceable_type plan_id group_id] }
|
validates :priceable_id, uniqueness: { scope: %i[priceable_type plan_id group_id] }
|
||||||
|
|
||||||
class << self
|
|
||||||
|
|
||||||
##
|
|
||||||
# @param admin {Boolean} true if the current user (ie.the user who requests the price) is an admin
|
|
||||||
# @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 slots {Array<Slot>} when did the reservation will occur
|
|
||||||
# @param options {plan_id:Number, nb_places:Number, tickets:Array<Ticket>, coupon_code:String, payment_schedule:Boolean}
|
|
||||||
# - plan_id {Number} if the user is subscribing to a plan at the same time of his reservation, specify the plan's ID here
|
|
||||||
# - nb_places {Number} for _reservable_ of type Event, pass here the number of booked places
|
|
||||||
# - 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
|
|
||||||
##
|
|
||||||
def compute(admin, user, reservable, slots, options = {})
|
|
||||||
total_amount = 0
|
|
||||||
all_elements = {}
|
|
||||||
all_elements[:slots] = []
|
|
||||||
|
|
||||||
# initialize Plan
|
|
||||||
plan = if user.subscribed_plan
|
|
||||||
new_plan_being_bought = false
|
|
||||||
user.subscribed_plan
|
|
||||||
elsif options[:plan_id]
|
|
||||||
new_plan_being_bought = true
|
|
||||||
Plan.find(options[:plan_id])
|
|
||||||
else
|
|
||||||
new_plan_being_bought = false
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# === compute reservation price ===
|
|
||||||
|
|
||||||
case reservable
|
|
||||||
|
|
||||||
# Machine reservation
|
|
||||||
when Machine
|
|
||||||
base_amount = reservable.prices.find_by(group_id: user.group_id, plan_id: plan.try(:id)).amount
|
|
||||||
if plan
|
|
||||||
machine_credit = plan.machine_credits.select { |credit| credit.creditable_id == reservable.id }.first
|
|
||||||
if machine_credit
|
|
||||||
hours_available = credits_hours(machine_credit, user, new_plan_being_bought)
|
|
||||||
slots.each_with_index do |slot, index|
|
|
||||||
total_amount += get_slot_price(base_amount, slot, admin, elements: all_elements, has_credits: (index < hours_available))
|
|
||||||
end
|
|
||||||
else
|
|
||||||
slots.each do |slot|
|
|
||||||
total_amount += get_slot_price(base_amount, slot, admin, elements: all_elements)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
slots.each do |slot|
|
|
||||||
total_amount += get_slot_price(base_amount, slot, admin, elements: all_elements)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Training reservation
|
|
||||||
when Training
|
|
||||||
amount = reservable.amount_by_group(user.group_id).amount
|
|
||||||
if plan
|
|
||||||
# Return True if the subscription link a training credit for training reserved by the user
|
|
||||||
space_is_creditable = plan.training_credits.select { |credit| credit.creditable_id == reservable.id }.any?
|
|
||||||
|
|
||||||
# Training reserved by the user is free when :
|
|
||||||
|
|
||||||
# |-> the user already has a current subscription and if space_is_creditable is true and has at least one credit available.
|
|
||||||
if !new_plan_being_bought
|
|
||||||
amount = 0 if user.training_credits.size < plan.training_credit_nb && space_is_creditable
|
|
||||||
# |-> the user buys a new subscription and if space_is_creditable is true.
|
|
||||||
else
|
|
||||||
amount = 0 if space_is_creditable
|
|
||||||
end
|
|
||||||
end
|
|
||||||
slots.each do |slot|
|
|
||||||
total_amount += get_slot_price(amount, slot, admin, elements: all_elements, is_division: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Event reservation
|
|
||||||
when Event
|
|
||||||
amount = reservable.amount * options[:nb_places]
|
|
||||||
options[:tickets]&.each do |ticket|
|
|
||||||
amount += ticket[:booked] * EventPriceCategory.find(ticket[:event_price_category_id]).amount
|
|
||||||
end
|
|
||||||
slots.each do |slot|
|
|
||||||
total_amount += get_slot_price(amount, slot, admin, elements: all_elements, is_division: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Space reservation
|
|
||||||
when Space
|
|
||||||
base_amount = reservable.prices.find_by(group_id: user.group_id, plan_id: plan.try(:id)).amount
|
|
||||||
|
|
||||||
if plan
|
|
||||||
space_credit = plan.space_credits.select { |credit| credit.creditable_id == reservable.id }.first
|
|
||||||
if space_credit
|
|
||||||
hours_available = credits_hours(space_credit, user, new_plan_being_bought)
|
|
||||||
slots.each_with_index do |slot, index|
|
|
||||||
total_amount += get_slot_price(base_amount, slot, admin, elements: all_elements, has_credits: (index < hours_available))
|
|
||||||
end
|
|
||||||
else
|
|
||||||
slots.each do |slot|
|
|
||||||
total_amount += get_slot_price(base_amount, slot, admin, elements: all_elements)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
slots.each do |slot|
|
|
||||||
total_amount += get_slot_price(base_amount, slot, admin, elements: all_elements)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# No reservation (only subscription)
|
|
||||||
when nil
|
|
||||||
total_amount = 0
|
|
||||||
|
|
||||||
# Unknown reservation type
|
|
||||||
else
|
|
||||||
raise NotImplementedError
|
|
||||||
end
|
|
||||||
|
|
||||||
# === compute Plan price (if any) ===
|
|
||||||
unless options[:plan_id].nil?
|
|
||||||
all_elements[:plan] = plan.amount
|
|
||||||
total_amount += plan.amount
|
|
||||||
end
|
|
||||||
|
|
||||||
# === apply Coupon if any ===
|
|
||||||
_amount_no_coupon = total_amount
|
|
||||||
cs = CouponService.new
|
|
||||||
cp = cs.validate(options[:coupon_code], user.id)
|
|
||||||
total_amount = cs.apply(total_amount, cp)
|
|
||||||
|
|
||||||
# == generate PaymentSchedule (if applicable) ===
|
|
||||||
schedule = if options[:payment_schedule] && plan&.monthly_payment
|
|
||||||
PaymentScheduleService.new.compute(plan, _amount_no_coupon, coupon: cp)
|
|
||||||
else
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
total_amount = schedule[:items][0].amount if schedule
|
|
||||||
|
|
||||||
# return result
|
|
||||||
{
|
|
||||||
elements: all_elements,
|
|
||||||
total: total_amount.to_i,
|
|
||||||
before_coupon: _amount_no_coupon.to_i,
|
|
||||||
coupon: cp,
|
|
||||||
schedule: schedule
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
GET_SLOT_PRICE_DEFAULT_OPTS = { has_credits: false, elements: nil, is_division: true }.freeze
|
|
||||||
##
|
|
||||||
# Compute the price of a single slot, according to the base price and the ability for an admin
|
|
||||||
# to offer the slot.
|
|
||||||
# @param hourly_rate {Number} base price of a slot
|
|
||||||
# @param slot {Hash} Slot object
|
|
||||||
# @param is_admin {Boolean} true if the current user has the 'admin' role
|
|
||||||
# @param [options] {Hash} optional parameters, allowing the following options:
|
|
||||||
# - elements {Array} if provided the resulting price will be append into elements.slots
|
|
||||||
# - has_credits {Boolean} true if the user still has credits for the given slot, false if not provided
|
|
||||||
# - is_division {boolean} false if the slot covers an full availability, true if it is a subdivision (default)
|
|
||||||
# @return {Number} price of the slot
|
|
||||||
##
|
|
||||||
def get_slot_price(hourly_rate, slot, is_admin, options = {})
|
|
||||||
options = GET_SLOT_PRICE_DEFAULT_OPTS.merge(options)
|
|
||||||
|
|
||||||
slot_rate = options[:has_credits] || (slot[:offered] && is_admin) ? 0 : hourly_rate
|
|
||||||
real_price = if options[:is_division]
|
|
||||||
(slot_rate / MINUTES_PER_HOUR) * ((slot[:end_at].to_time - slot[:start_at].to_time) / SECONDS_PER_MINUTE)
|
|
||||||
else
|
|
||||||
slot_rate
|
|
||||||
end
|
|
||||||
|
|
||||||
unless options[:elements].nil?
|
|
||||||
options[:elements][:slots].push(
|
|
||||||
start_at: slot[:start_at],
|
|
||||||
price: real_price,
|
|
||||||
promo: (slot_rate != hourly_rate)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
real_price
|
|
||||||
end
|
|
||||||
|
|
||||||
##
|
|
||||||
# Compute the number of remaining hours in the users current credits (for machine or space)
|
|
||||||
##
|
|
||||||
def credits_hours(credits, user, new_plan_being_bought)
|
|
||||||
hours_available = credits.hours
|
|
||||||
unless new_plan_being_bought
|
|
||||||
user_credit = user.users_credits.find_by(credit_id: credits.id)
|
|
||||||
hours_available = credits.hours - user_credit.hours_used if user_credit
|
|
||||||
end
|
|
||||||
hours_available
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
@ -8,7 +8,7 @@ class ShoppingCart
|
|||||||
# @param coupon {CartItem::Coupon}
|
# @param coupon {CartItem::Coupon}
|
||||||
# @param payment_schedule {CartItem::PaymentSchedule}
|
# @param payment_schedule {CartItem::PaymentSchedule}
|
||||||
# @param customer {User}
|
# @param customer {User}
|
||||||
def initialize(customer, coupon, payment_method = '', items: [], payment_schedule: nil)
|
def initialize(customer, coupon, payment_schedule, payment_method = '', items: [])
|
||||||
raise TypeError unless customer.class == User
|
raise TypeError unless customer.class == User
|
||||||
|
|
||||||
@customer = customer
|
@customer = customer
|
||||||
@ -18,6 +18,7 @@ class ShoppingCart
|
|||||||
@payment_schedule = payment_schedule
|
@payment_schedule = payment_schedule
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# compute the price details of the current shopping cart
|
||||||
def total
|
def total
|
||||||
total_amount = 0
|
total_amount = 0
|
||||||
all_elements = { slots: [] }
|
all_elements = { slots: [] }
|
||||||
|
@ -87,8 +87,10 @@ class Subscription < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def original_payment_schedule
|
def original_payment_schedule
|
||||||
|
# if the payment schedule was associated with this subscription, return it directly
|
||||||
return payment_schedule if payment_schedule
|
return payment_schedule if payment_schedule
|
||||||
|
|
||||||
|
# if it was associated with a reservation, query payment schedule from one of its items
|
||||||
PaymentScheduleItem.where("cast(details->>'subscription_id' AS int) = ?", id).first&.payment_schedule
|
PaymentScheduleItem.where("cast(details->>'subscription_id' AS int) = ?", id).first&.payment_schedule
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -6,6 +6,10 @@ class CartService
|
|||||||
@operator = operator
|
@operator = operator
|
||||||
end
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# For details about the expected hash format
|
||||||
|
# @see app/frontend/src/javascript/models/payment.ts > interface CartItems
|
||||||
|
##
|
||||||
def from_hash(cart_items)
|
def from_hash(cart_items)
|
||||||
items = []
|
items = []
|
||||||
plan_info = plan(cart_items)
|
plan_info = plan(cart_items)
|
||||||
@ -21,9 +25,9 @@ class CartService
|
|||||||
ShoppingCart.new(
|
ShoppingCart.new(
|
||||||
@customer,
|
@customer,
|
||||||
coupon,
|
coupon,
|
||||||
|
schedule,
|
||||||
cart_items[:payment_method],
|
cart_items[:payment_method],
|
||||||
items: items,
|
items: items
|
||||||
payment_schedule: schedule
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ class InvoicesService
|
|||||||
|
|
||||||
##
|
##
|
||||||
# Create an Invoice with an associated array of InvoiceItem matching the given parameters
|
# Create an Invoice with an associated array of InvoiceItem matching the given parameters
|
||||||
# @param payment_details {Hash} as generated by Price.compute
|
# @param payment_details {Hash} as generated by ShoppingCart.total
|
||||||
# @param operator_profile_id {Number} ID of the user that operates the invoice generation (may be an admin, a manager or the customer himself)
|
# @param operator_profile_id {Number} ID of the user that operates the invoice generation (may be an admin, a manager or the customer himself)
|
||||||
# @param reservation {Reservation} the booking reservation, if any
|
# @param reservation {Reservation} the booking reservation, if any
|
||||||
# @param subscription {Subscription} the booking subscription, if any
|
# @param subscription {Subscription} the booking subscription, if any
|
||||||
@ -78,15 +78,18 @@ class InvoicesService
|
|||||||
operator&.admin? || (operator&.manager? && operator != user) ? nil : Setting.get('payment_gateway')
|
operator&.admin? || (operator&.manager? && operator != user) ? nil : Setting.get('payment_gateway')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
pgo = unless payment_id.nil?
|
||||||
|
{
|
||||||
|
gateway_object_id: payment_id,
|
||||||
|
gateway_object_type: payment_type
|
||||||
|
}
|
||||||
|
end
|
||||||
invoice = Invoice.new(
|
invoice = Invoice.new(
|
||||||
invoiced: subscription || reservation,
|
invoiced: subscription || reservation,
|
||||||
invoicing_profile: user.invoicing_profile,
|
invoicing_profile: user.invoicing_profile,
|
||||||
statistic_profile: user.statistic_profile,
|
statistic_profile: user.statistic_profile,
|
||||||
operator_profile_id: operator_profile_id,
|
operator_profile_id: operator_profile_id,
|
||||||
payment_gateway_object_attributes: {
|
payment_gateway_object_attributes: pgo,
|
||||||
gateway_object_id: payment_id,
|
|
||||||
gateway_object_type: payment_type
|
|
||||||
},
|
|
||||||
payment_method: method
|
payment_method: method
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -98,7 +101,7 @@ class InvoicesService
|
|||||||
##
|
##
|
||||||
# Generate an array of {InvoiceItem} with the elements in provided reservation, price included.
|
# Generate an array of {InvoiceItem} with the elements in provided reservation, price included.
|
||||||
# @param invoice {Invoice} the parent invoice
|
# @param invoice {Invoice} the parent invoice
|
||||||
# @param payment_details {Hash} as generated by Price.compute
|
# @param payment_details {Hash} as generated by ShoppingCart.total
|
||||||
##
|
##
|
||||||
def self.generate_invoice_items(invoice, payment_details, reservation: nil, subscription: nil)
|
def self.generate_invoice_items(invoice, payment_details, reservation: nil, subscription: nil)
|
||||||
if reservation
|
if reservation
|
||||||
|
@ -60,10 +60,12 @@ class PaymentScheduleService
|
|||||||
|
|
||||||
ps.scheduled = reservation || subscription
|
ps.scheduled = reservation || subscription
|
||||||
ps.payment_method = payment_method
|
ps.payment_method = payment_method
|
||||||
ps.payment_gateway_object = {
|
if !payment_id.nil? && !payment_type.nil?
|
||||||
gateway_object_id: payment_id,
|
ps.payment_gateway_object = {
|
||||||
gateway_object_type: payment_type
|
gateway_object_id: payment_id,
|
||||||
}
|
gateway_object_type: payment_type
|
||||||
|
}
|
||||||
|
end
|
||||||
ps.operator_profile = operator.invoicing_profile
|
ps.operator_profile = operator.invoicing_profile
|
||||||
ps.invoicing_profile = user.invoicing_profile
|
ps.invoicing_profile = user.invoicing_profile
|
||||||
ps.statistic_profile = user.statistic_profile
|
ps.statistic_profile = user.statistic_profile
|
||||||
|
@ -11,7 +11,7 @@ class Subscriptions::Subscribe
|
|||||||
|
|
||||||
##
|
##
|
||||||
# @param subscription {Subscription}
|
# @param subscription {Subscription}
|
||||||
# @param payment_details {Hash} as generated by Price.compute
|
# @param payment_details {Hash} as generated by ShoppingCart.total
|
||||||
# @param payment_id {String} from the payment gateway
|
# @param payment_id {String} from the payment gateway
|
||||||
# @param payment_type {String} the object type of payment_id
|
# @param payment_type {String} the object type of payment_id
|
||||||
# @param schedule {Boolean}
|
# @param schedule {Boolean}
|
||||||
@ -61,21 +61,27 @@ class Subscriptions::Subscribe
|
|||||||
)
|
)
|
||||||
if new_sub.save
|
if new_sub.save
|
||||||
schedule = subscription.original_payment_schedule
|
schedule = subscription.original_payment_schedule
|
||||||
details = Price.compute(true, new_sub.user, nil, [], plan_id: subscription.plan_id)
|
|
||||||
|
cs = CartService.new(current_user)
|
||||||
|
cart = cs.from_hash(customer_id: @user_id,
|
||||||
|
subscription: {
|
||||||
|
plan_id: subscription.plan_id
|
||||||
|
},
|
||||||
|
payment_schedule: !schedule.nil?)
|
||||||
|
details = cart.total
|
||||||
|
|
||||||
payment = if schedule
|
payment = if schedule
|
||||||
generate_schedule(subscription: new_sub,
|
generate_schedule(subscription: new_sub,
|
||||||
total: details[:before_coupon],
|
total: details[:before_coupon],
|
||||||
operator_profile_id: operator_profile_id,
|
operator_profile_id: operator_profile_id,
|
||||||
user: new_sub.user,
|
user: new_sub.user,
|
||||||
payment_method: schedule.payment_method,
|
payment_method: schedule.payment_method,
|
||||||
payment_id: schedule.gateway_object('Stripe::SetupIntent').id)
|
payment_id: schedule.gateway_payment_mean&.id,
|
||||||
|
payment_type: schedule.gateway_payment_mean&.class)
|
||||||
else
|
else
|
||||||
generate_invoice(subscription,
|
generate_invoice(subscription,
|
||||||
operator_profile_id,
|
operator_profile_id,
|
||||||
details,
|
details)
|
||||||
payment_id: schedule.gateway_object('Stripe::SetupIntent').id,
|
|
||||||
payment_type: 'Stripe::SetupIntent',
|
|
||||||
payment_method: schedule.payment_method)
|
|
||||||
end
|
end
|
||||||
payment.save
|
payment.save
|
||||||
payment.post_save(schedule&.stp_setup_intent_id)
|
payment.post_save(schedule&.stp_setup_intent_id)
|
||||||
|
@ -28,7 +28,7 @@ class PayZen::Helper
|
|||||||
end
|
end
|
||||||
|
|
||||||
## Generate a hash map compatible with PayZen 'V4/Customer/Customer'
|
## Generate a hash map compatible with PayZen 'V4/Customer/Customer'
|
||||||
def generate_customer(customer_id)
|
def generate_customer(customer_id, cart_items)
|
||||||
customer = User.find(customer_id)
|
customer = User.find(customer_id)
|
||||||
address = if customer.organization?
|
address = if customer.organization?
|
||||||
customer.invoicing_profile.organization.address&.address
|
customer.invoicing_profile.organization.address&.address
|
||||||
@ -48,18 +48,24 @@ class PayZen::Helper
|
|||||||
shippingDetails: {
|
shippingDetails: {
|
||||||
category: customer.organization? ? 'COMPANY' : 'PRIVATE',
|
category: customer.organization? ? 'COMPANY' : 'PRIVATE',
|
||||||
shippingMethod: 'ETICKET'
|
shippingMethod: 'ETICKET'
|
||||||
}
|
},
|
||||||
|
shoppingCart: generate_shopping_cart(cart_items, customer)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
## Generate a hash map compatible with PayZen 'V4/Customer/ShoppingCart'
|
## Generate a hash map compatible with PayZen 'V4/Customer/ShoppingCart'
|
||||||
def generate_shopping_cart(cart_items, customer)
|
def generate_shopping_cart(cart_items, customer)
|
||||||
|
cs = CartService.new(current_user)
|
||||||
|
cart = cs.from_hash(cart_items)
|
||||||
{
|
{
|
||||||
cartItemInfo: cart_items.map do |type, value|
|
cartItemInfo: cart.items.map do |item|
|
||||||
{
|
{
|
||||||
productAmount: item.
|
productAmount: item.price,
|
||||||
productType: customer.organization? ? 'SERVICE_FOR_BUSINESS' : 'SERVICE_FOR_INDIVIDUAL',
|
productLabel: item.name,
|
||||||
|
productQty: 1,
|
||||||
|
productType: customer.organization? ? 'SERVICE_FOR_BUSINESS' : 'SERVICE_FOR_INDIVIDUAL'
|
||||||
}
|
}
|
||||||
|
end
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -9,9 +9,13 @@ module PayZen; end
|
|||||||
class PayZen::Item < Payment::Item
|
class PayZen::Item < Payment::Item
|
||||||
attr_accessor :id
|
attr_accessor :id
|
||||||
|
|
||||||
def retrieve(id)
|
def retrieve(id = nil)
|
||||||
@id ||= id
|
@id ||= id
|
||||||
client = klass.constantize
|
client = klass.constantize
|
||||||
client.get(@id)
|
client.get(@id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def payment_mean?
|
||||||
|
klass == 'PayZen::Token'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
19
lib/pay_zen/token.rb
Normal file
19
lib/pay_zen/token.rb
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'pay_zen/client'
|
||||||
|
|
||||||
|
# Token/* endpoints of the PayZen REST API
|
||||||
|
class PayZen::Token < PayZen::Client
|
||||||
|
def initialize(base_url: nil, username: nil, password: nil)
|
||||||
|
super(base_url: base_url, username: username, password: password)
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# @see https://payzen.io/en-EN/rest/V4.0/api/playground/Token/Get/
|
||||||
|
##
|
||||||
|
def get(payment_method_token)
|
||||||
|
post('/Token/Get/', paymentMethodToken: payment_method_token)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
@ -7,13 +7,18 @@ module Payment; end
|
|||||||
class Payment::Item
|
class Payment::Item
|
||||||
attr_reader :klass
|
attr_reader :klass
|
||||||
|
|
||||||
def initialize(klass)
|
def initialize(klass, id = nil)
|
||||||
@klass = klass
|
@klass = klass
|
||||||
|
@id = id
|
||||||
end
|
end
|
||||||
|
|
||||||
def class
|
def class
|
||||||
klass
|
klass
|
||||||
end
|
end
|
||||||
|
|
||||||
def retrieve(_id); end
|
def payment_mean?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def retrieve(_id = nil); end
|
||||||
end
|
end
|
||||||
|
@ -10,19 +10,19 @@ module Payment; end
|
|||||||
class Payment::ItemBuilder
|
class Payment::ItemBuilder
|
||||||
attr_reader :instance
|
attr_reader :instance
|
||||||
|
|
||||||
def self.build(klass)
|
def self.build(klass, id = nil)
|
||||||
builder = new(klass)
|
builder = new(klass, id)
|
||||||
builder.instance
|
builder.instance
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def initialize(klass)
|
def initialize(klass, id = nil)
|
||||||
@instance = case klass
|
@instance = case klass
|
||||||
when /^PayZen::/
|
when /^PayZen::/
|
||||||
PayZen::Item.new(klass)
|
PayZen::Item.new(klass, id)
|
||||||
when /^Stripe::/
|
when /^Stripe::/
|
||||||
Stripe::Item.new(klass)
|
Stripe::Item.new(klass, id)
|
||||||
else
|
else
|
||||||
raise TypeError
|
raise TypeError
|
||||||
end
|
end
|
||||||
|
@ -9,8 +9,12 @@ module Stripe; end
|
|||||||
class Stripe::Item < Payment::Item
|
class Stripe::Item < Payment::Item
|
||||||
attr_accessor :id
|
attr_accessor :id
|
||||||
|
|
||||||
def retrieve(id)
|
def retrieve(id = nil)
|
||||||
@id ||= id
|
@id ||= id
|
||||||
klass.constantize.retrieve(@id, api_key: Setting.get('stripe_secret_key'))
|
klass.constantize.retrieve(@id, api_key: Setting.get('stripe_secret_key'))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def payment_mean?
|
||||||
|
klass == 'Stripe::SetupIntent'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user