Created
December 31, 2013 08:59
-
-
Save clupprich/8194257 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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