Skip to content

Instantly share code, notes, and snippets.

@clupprich
Created December 31, 2013 08:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save clupprich/8194257 to your computer and use it in GitHub Desktop.
Save clupprich/8194257 to your computer and use it in GitHub Desktop.
# encoding:utf-8
class Order < ActiveRecord::Base
acts_as_paranoid
attr_accessible :coupon_code, :plan, :subscription, :ordered_at, :address_attributes
attr_accessor :coupon_code, :plan
has_many :line_items, dependent: :destroy
has_many :transactions, dependent: :destroy
has_many :wirecard_transactions, source: :transactions
has_many :direct_debit_transactions, source: :transactions
belongs_to :address
has_many :invoices, through: :transactions
has_many :orders, class_name: 'Order', foreign_key: 'order_id'
belongs_to :order, class_name: 'Order'
accepts_nested_attributes_for :address
belongs_to :subscription
scope :by_type, -> type { where(type: type) }
scope :by_state, -> state { where(state: state) }
scope :purchased_within, -> interval { where(['DATE(purchased_at) = DATE(?)', interval.to_interval.ago]) }
scope :purchased_today, -> { where(['DATE(purchased_at) >= DATE(?)', Time.zone.now]).where(state: :completed) }
state_machine initial: :initial do
before_transition initial: :review, do: :calculate_price
before_transition review: :completed, do: :update_order_timestamps
after_transition review: :completed, do: :activate_subscription
after_transition review: :completed, do: :redeem_coupon
before_transition payment: :completed, do: :update_order_timestamps
after_transition payment: :completed, do: :activate_subscription
after_transition payment: :completed, do: :redeem_coupon
after_transition any => :completed, do: :synchronize_services
after_transition initial: :review do |order, transition|
order.ordered_at = Time.zone.now
subscription = order.subscription
if subscription.bundle.present? && subscription.bundle != order.plan.bundle
Raven.capture_message("Subscriptions's bundle does not equal order bundle!",
extra: {
subscription_id: subscription.id,
order_id: order.id,
}
)
end
order.subscription.update_attribute(:bundle, order.plan.bundle)
end
event :close do
transition initial: :review
end
event :reopen do
transition review: :initial
end
event :deny do
transition any => :denied
end
event :purchase do
transition review: :completed, if: lambda { |order| order.zero? }
transition review: :payment
end
event :confirm do
transition payment: :completed
end
state :initial do
validates :subscription, presence: true
validates_associated :address
def add_plan(plan)
return if plan.nil?
@plan = plan
self.line_items << PlanLineItem.new(plan: plan)
update_tax
#self.save!
end
def add_coupon(coupon)
return if coupon.nil?
self.line_items << CouponLineItem.new(coupon: coupon)
update_tax
#self.save!
end
end
state :review, :payment do
validates :subscription, presence: true
validates :price, presence: true
validate { |order| order.validate_plan_assigned }
validate { |order| order.validate_coupon_code }
def validate_plan_assigned
errors.add(:plan) unless plan
end
def validate_coupon_code
errors.add(:coupon_code) unless valid_coupon?(coupon)
end
end
state :completed do
validates :subscription, presence: true
validates :price, presence: true
validates :ordered_at, presence: true
validates :valid_from, presence: true
validates :valid_until, presence: true
validates :transactions, presence: true, unless: lambda { |order| order.zero? }
end
end
def self.new_with_plan(plan)
self.new(plan: plan)
end
def plan_line_items
line_items.select { |line_item| line_item.type == 'PlanLineItem' }
end
def coupon_line_items
line_items.select { |line_item| line_item.type == 'CouponLineItem' }
end
def tax_line_item
line_items.select { |line_item| line_item.type == 'TaxLineItem' }.first
end
def zero?
self.price == 0
end
def plan
@plan ||= plan_line_items.first.try(:plan) || nil
end
def product
# TODO For now this is it
@product ||= plan.bundle.products.first.product_identifier.to_sym
rescue
:index
end
def products
@produts ||= plan.bundle.products.map(&:product_identifier).map(&:to_sym)
end
def coupon
coupon_line_items.first.try(:coupon) || nil
end
def user
return if self.subscription.nil?
self.subscription.user
end
def add_transaction(transaction)
transaction.order = self
self.transactions << transaction
end
def align(valid_from, valid_until)
self.valid_from = valid_from
self.valid_until = valid_until
end
def aligned?(valid_from, valid_until)
valid_until == self.valid_until
end
def self.completed
where(state: :completed)
end
def self.with_line_items
includes(:line_items)
end
def self.with_subscriptions
includes(subscription: :user)
end
def self.ending_within(timespan)
completed.where(['valid_until < ?', Time.zone.now + timespan])
end
def self.by_week_and_year
t = self.arel_table
self.completed
.select(t.function(:weekofyear, t[:purchased_at], 'week'))
.select(t.function(:year, t[:purchased_at], 'year'))
.select(t.function(:sum, t[:price], 'revenue'))
.group(:week, :year)
.order(:year, :week)
end
def preview!
calculate_price
active_subscription = subscription.user.subscriptions.has_active_subscription(*self.plan.products)
self.valid_from ||= active_subscription.try(:ends_at) || Time.zone.now
self.valid_until ||= get_valid_until
!!(self.subscription && self.price && valid_coupon?(coupon) && plan)
end
def price_net
plan_line_items.map(&:value).sum + coupon_line_items.map(&:value).sum
end
def tax
tax_line_item.try(:value) || 0.0
end
def price_without_share
self.price_net * price_share
end
def price_share
1.0
end
def affiliation
'Web'
end
def human_type
type.gsub('Order', '')
end
def destroyable?
state.eql?('denied')
end
private
def calculate_price
update_tax
t_price = plan_prices + coupon_prices
self.price = t_price + tax_line_item.value
end
def plan_prices
plan_line_items.map(&:value).sum
end
def coupon_prices
coupon_line_items.map { |line_item| line_item.calculate_price(plan_prices) }
coupon_line_items.map(&:save)
coupon_prices = coupon_line_items.map(&:value).sum
end
def update_tax
unless tax_line_item.present?
line_items << TaxLineItem.new
end
t_price = plan_prices + coupon_prices
country = address.present? ? address.country : I18n.locale
if address.blank?
tax_line_item.calculate_tax(t_price, country)
elsif address.is_tax_free?
tax_line_item.clear
else
tax_line_item.calculate_tax(t_price, country)
end
end
def update_order_timestamps
self.purchased_at = Time.zone.now
active_subscription = subscription.user.subscriptions.has_active_subscription(*self.plan.products)
self.valid_from ||= active_subscription.try(:ends_at) || Time.zone.now
self.valid_until ||= get_valid_until
end
def get_valid_until
interval = plan.interval
return self.valid_from if interval.blank?
self.valid_from + interval.to_interval
end
def activate_subscription
MixpanelService.new.perform(user, 'Payment Success') if trial_or_paid == :paid
subscription.activate!(plan, self)
end
def redeem_coupon
self.coupon_line_items.map { |line_item| line_item.coupon.redeem_for(user) }
end
def valid_coupon?(coupon)
return true if coupon.blank?
coupon.redeemable_for?(user, plan)
end
def remove_coupon(coupon)
return if coupon.nil?
CouponLineItem.where(order_id: self.id, coupon_id: coupon.id).first.destroy
self.reload if self.persisted?
calculate_price
end
def synchronize_services
PaymentServicesWorker.perform_async(:has_ordered, self.id)
end
def trial_or_paid
:paid
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment