2016-03-23 18:39:41 +01:00
|
|
|
class Subscription < ActiveRecord::Base
|
|
|
|
include NotifyWith::NotificationAttachedObject
|
|
|
|
|
|
|
|
belongs_to :plan
|
|
|
|
belongs_to :user
|
|
|
|
|
|
|
|
has_many :invoices, as: :invoiced, dependent: :destroy
|
|
|
|
has_many :offer_days, dependent: :destroy
|
|
|
|
|
|
|
|
validates_presence_of :plan_id
|
2016-04-07 12:28:25 +02:00
|
|
|
validates_with SubscriptionGroupValidator
|
2016-03-23 18:39:41 +01:00
|
|
|
|
|
|
|
attr_accessor :card_token
|
|
|
|
|
|
|
|
# creation
|
|
|
|
after_save :notify_member_subscribed_plan, if: :is_new?
|
|
|
|
after_save :notify_admin_subscribed_plan, if: :is_new?
|
|
|
|
after_save :notify_partner_subscribed_plan, if: :of_partner_plan?
|
|
|
|
|
|
|
|
# Stripe subscription payment
|
2016-08-11 18:17:28 +02:00
|
|
|
def save_with_payment(invoice = true, coupon_code = nil)
|
2016-03-23 18:39:41 +01:00
|
|
|
if valid?
|
|
|
|
customer = Stripe::Customer.retrieve(user.stp_customer_id)
|
|
|
|
begin
|
2016-07-08 17:24:51 +02:00
|
|
|
# dont add a wallet invoice item if pay subscription by reservation
|
|
|
|
if invoice
|
|
|
|
@wallet_amount_debit = get_wallet_amount_debit
|
|
|
|
if @wallet_amount_debit != 0
|
|
|
|
Stripe::InvoiceItem.create(
|
|
|
|
customer: user.stp_customer_id,
|
|
|
|
amount: -@wallet_amount_debit,
|
|
|
|
currency: Rails.application.secrets.stripe_currency,
|
|
|
|
description: "wallet -#{@wallet_amount_debit / 100.0}"
|
|
|
|
)
|
|
|
|
end
|
2016-08-11 18:17:28 +02:00
|
|
|
|
|
|
|
unless coupon_code.nil?
|
|
|
|
cp = Coupon.find_by_code(coupon_code)
|
2016-09-08 15:19:12 +02:00
|
|
|
if not cp.nil? and cp.status(user.id) == 'active'
|
|
|
|
total = plan.amount
|
|
|
|
Stripe::InvoiceItem.create(
|
|
|
|
customer: user.stp_customer_id,
|
|
|
|
amount: -(total * cp.percent_off / 100.0).to_i,
|
|
|
|
currency: Rails.application.secrets.stripe_currency,
|
|
|
|
description: "coupon #{cp.code}"
|
|
|
|
)
|
|
|
|
else
|
|
|
|
raise InvalidCouponError
|
|
|
|
end
|
|
|
|
end
|
|
|
|
elsif coupon_code != nil
|
|
|
|
# this case applies if a subscription was took in addition of a reservation, so we create a second
|
|
|
|
# stripe coupon to apply the discount on the subscription item for the stripe's invoice.
|
|
|
|
cp = Coupon.find_by_code(coupon_code)
|
|
|
|
if not cp.nil? and cp.status(user.id) == 'active'
|
2016-08-11 18:17:28 +02:00
|
|
|
total = plan.amount
|
|
|
|
Stripe::InvoiceItem.create(
|
|
|
|
customer: user.stp_customer_id,
|
2016-09-01 11:36:51 +02:00
|
|
|
amount: -(total * cp.percent_off / 100.0).to_i,
|
2016-08-11 18:17:28 +02:00
|
|
|
currency: Rails.application.secrets.stripe_currency,
|
|
|
|
description: "coupon #{cp.code}"
|
|
|
|
)
|
2016-09-08 15:19:12 +02:00
|
|
|
else
|
|
|
|
raise InvalidCouponError
|
2016-08-11 18:17:28 +02:00
|
|
|
end
|
2016-07-08 17:24:51 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
new_subscription = customer.subscriptions.create(plan: plan.stp_plan_id, source: card_token)
|
2016-03-23 18:39:41 +01:00
|
|
|
self.stp_subscription_id = new_subscription.id
|
|
|
|
self.canceled_at = nil
|
|
|
|
self.expired_at = Time.at(new_subscription.current_period_end)
|
|
|
|
save!
|
|
|
|
|
2016-04-07 16:02:09 +02:00
|
|
|
UsersCredits::Manager.new(user: self.user).reset_credits if expired_date_changed
|
2016-03-23 18:39:41 +01:00
|
|
|
|
2016-03-30 15:37:33 +02:00
|
|
|
# generate invoice
|
2016-03-23 18:39:41 +01:00
|
|
|
stp_invoice = Stripe::Invoice.all(customer: user.stp_customer_id, limit: 1).data.first
|
2016-07-19 11:37:01 +02:00
|
|
|
if invoice
|
2016-08-11 18:17:28 +02:00
|
|
|
invoc = generate_invoice(stp_invoice.id, coupon_code)
|
2016-07-19 11:37:01 +02:00
|
|
|
# debit wallet
|
2016-07-20 15:07:43 +02:00
|
|
|
wallet_transaction = debit_user_wallet
|
|
|
|
if wallet_transaction
|
|
|
|
invoc.wallet_amount = @wallet_amount_debit
|
|
|
|
invoc.wallet_transaction_id = wallet_transaction.id
|
|
|
|
end
|
2016-07-19 11:37:01 +02:00
|
|
|
invoc.save
|
|
|
|
end
|
2016-03-23 18:39:41 +01:00
|
|
|
# cancel subscription after create
|
|
|
|
cancel
|
|
|
|
return true
|
|
|
|
rescue Stripe::CardError => card_error
|
|
|
|
logger.error card_error
|
|
|
|
errors[:card] << card_error.message
|
|
|
|
return false
|
|
|
|
rescue Stripe::InvalidRequestError => e
|
|
|
|
# Invalid parameters were supplied to Stripe's API
|
|
|
|
logger.error e
|
|
|
|
errors[:payment] << e.message
|
|
|
|
return false
|
|
|
|
rescue Stripe::AuthenticationError => e
|
|
|
|
# Authentication with Stripe's API failed
|
|
|
|
# (maybe you changed API keys recently)
|
|
|
|
logger.error e
|
|
|
|
errors[:payment] << e.message
|
|
|
|
return false
|
|
|
|
rescue Stripe::APIConnectionError => e
|
|
|
|
# Network communication with Stripe failed
|
|
|
|
logger.error e
|
|
|
|
errors[:payment] << e.message
|
|
|
|
return false
|
|
|
|
rescue Stripe::StripeError => e
|
|
|
|
# Display a very generic error to the user, and maybe send
|
|
|
|
# yourself an email
|
|
|
|
logger.error e
|
|
|
|
errors[:payment] << e.message
|
|
|
|
return false
|
|
|
|
rescue => e
|
|
|
|
# Something else happened, completely unrelated to Stripe
|
|
|
|
logger.error e
|
|
|
|
errors[:payment] << e.message
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-08-11 18:17:28 +02:00
|
|
|
def save_with_local_payment(invoice = true, coupon_code = nil)
|
2016-03-23 18:39:41 +01:00
|
|
|
if valid?
|
2016-07-08 17:24:51 +02:00
|
|
|
@wallet_amount_debit = get_wallet_amount_debit if invoice
|
|
|
|
|
2016-03-23 18:39:41 +01:00
|
|
|
self.stp_subscription_id = nil
|
|
|
|
self.canceled_at = nil
|
|
|
|
set_expired_at
|
|
|
|
save!
|
2016-04-07 16:02:09 +02:00
|
|
|
UsersCredits::Manager.new(user: self.user).reset_credits if expired_date_changed
|
2016-07-19 11:37:01 +02:00
|
|
|
if invoice
|
2016-08-11 18:17:28 +02:00
|
|
|
invoc = generate_invoice(nil, coupon_code)
|
2016-07-19 11:37:01 +02:00
|
|
|
# debit wallet
|
2016-07-20 15:07:43 +02:00
|
|
|
wallet_transaction = debit_user_wallet
|
|
|
|
if wallet_transaction
|
|
|
|
invoc.wallet_amount = @wallet_amount_debit
|
|
|
|
invoc.wallet_transaction_id = wallet_transaction.id
|
|
|
|
end
|
2016-07-19 11:37:01 +02:00
|
|
|
invoc.save
|
|
|
|
end
|
2016-03-23 18:39:41 +01:00
|
|
|
return true
|
|
|
|
else
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-08-11 18:17:28 +02:00
|
|
|
def generate_invoice(stp_invoice_id = nil, coupon_code = nil)
|
|
|
|
coupon_id = nil
|
|
|
|
total = plan.amount
|
|
|
|
|
|
|
|
unless coupon_code.nil?
|
|
|
|
coupon = Coupon.find_by_code(coupon_code)
|
|
|
|
coupon_id = coupon.id
|
2016-08-16 10:29:08 +02:00
|
|
|
total = plan.amount - (plan.amount * coupon.percent_off / 100.0)
|
2016-08-11 18:17:28 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
invoice = Invoice.new(invoiced_id: id, invoiced_type: 'Subscription', user: user, total: total, stp_invoice_id: stp_invoice_id, coupon_id: coupon_id)
|
2016-03-23 18:39:41 +01:00
|
|
|
invoice.invoice_items.push InvoiceItem.new(amount: plan.amount, stp_invoice_item_id: stp_subscription_id, description: plan.name, subscription_id: self.id)
|
|
|
|
invoice
|
|
|
|
end
|
|
|
|
|
|
|
|
def generate_and_save_invoice(stp_invoice_id = nil)
|
|
|
|
generate_invoice(stp_invoice_id).save
|
|
|
|
end
|
|
|
|
|
|
|
|
def generate_and_save_offer_day_invoice(offer_day_start_at)
|
|
|
|
od = offer_days.create(start_at: offer_day_start_at, end_at: expired_at)
|
|
|
|
invoice = Invoice.new(invoiced_id: od.id, invoiced_type: 'OfferDay', user: user, total: 0)
|
|
|
|
invoice.invoice_items.push InvoiceItem.new(amount: 0, description: plan.name, subscription_id: self.id)
|
|
|
|
invoice.save
|
|
|
|
end
|
|
|
|
|
|
|
|
def cancel
|
|
|
|
if stp_subscription_id.present?
|
|
|
|
stp_subscription = stripe_subscription
|
|
|
|
stp_subscription.delete(at_period_end: true)
|
|
|
|
update_columns(canceled_at: Time.now)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def stripe_subscription
|
|
|
|
user.stripe_customer.subscriptions.retrieve(stp_subscription_id) if stp_subscription_id.present?
|
|
|
|
end
|
|
|
|
|
|
|
|
def expire(time)
|
|
|
|
if !is_expired?
|
|
|
|
update_columns(expired_at: time, canceled_at: time)
|
|
|
|
notify_admin_subscription_canceled
|
|
|
|
notify_member_subscription_canceled
|
|
|
|
true
|
|
|
|
else
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def is_expired?
|
|
|
|
expired_at <= Time.now
|
|
|
|
end
|
|
|
|
|
|
|
|
def extend_expired_date(expired_at, free_days = false)
|
|
|
|
return false if expired_at <= self.expired_at
|
|
|
|
|
|
|
|
self.expired_at = expired_at
|
|
|
|
if save
|
2016-04-07 16:02:09 +02:00
|
|
|
UsersCredits::Manager.new(user: self.user).reset_credits if !free_days
|
2016-03-23 18:39:41 +01:00
|
|
|
notify_subscription_extended(free_days)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
def notify_member_subscribed_plan
|
|
|
|
NotificationCenter.call type: 'notify_member_subscribed_plan',
|
|
|
|
receiver: user,
|
|
|
|
attached_object: self
|
|
|
|
end
|
|
|
|
|
|
|
|
def notify_admin_subscribed_plan
|
|
|
|
NotificationCenter.call type: 'notify_admin_subscribed_plan',
|
|
|
|
receiver: User.admins,
|
|
|
|
attached_object: self
|
|
|
|
end
|
|
|
|
|
|
|
|
def notify_admin_subscription_canceled
|
|
|
|
NotificationCenter.call type: 'notify_admin_subscription_canceled',
|
|
|
|
receiver: User.admins,
|
|
|
|
attached_object: self
|
|
|
|
end
|
|
|
|
|
|
|
|
def notify_member_subscription_canceled
|
|
|
|
NotificationCenter.call type: 'notify_member_subscription_canceled',
|
|
|
|
receiver: user,
|
|
|
|
attached_object: self
|
|
|
|
end
|
|
|
|
|
|
|
|
def notify_partner_subscribed_plan
|
|
|
|
NotificationCenter.call type: 'notify_partner_subscribed_plan',
|
|
|
|
receiver: plan.partners,
|
|
|
|
attached_object: self
|
|
|
|
end
|
|
|
|
|
|
|
|
def notify_subscription_extended(free_days)
|
|
|
|
meta_data = {}
|
|
|
|
meta_data[:free_days] = true if free_days == true
|
|
|
|
notification = Notification.new(meta_data: meta_data)
|
|
|
|
notification.send_notification(type: :notify_member_subscription_extended, attached_object: self).to(user).deliver_later
|
|
|
|
|
|
|
|
User.admins.each do |admin|
|
|
|
|
notification = Notification.new(meta_data: meta_data)
|
|
|
|
notification.send_notification(type: :notify_admin_subscription_extended, attached_object: self).to(admin).deliver_later
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# set a expired date by plan
|
|
|
|
# expired_at will be updated when has a new payment
|
|
|
|
def set_expired_at
|
|
|
|
start_at = Time.now
|
|
|
|
self.expired_at = start_at + plan.duration
|
|
|
|
end
|
|
|
|
|
|
|
|
def expired_date_changed
|
2016-03-30 15:37:33 +02:00
|
|
|
p_value = self.previous_changes[:expired_at][0]
|
|
|
|
return true if p_value.nil?
|
|
|
|
p_value.to_date != expired_at.to_date and expired_at > p_value
|
2016-03-23 18:39:41 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
# def is_being_extended?
|
|
|
|
# !expired_at_was.nil? and expired_at_changed?
|
|
|
|
# end
|
|
|
|
|
|
|
|
def is_new?
|
|
|
|
expired_at_was.nil?
|
|
|
|
end
|
|
|
|
|
|
|
|
def of_partner_plan?
|
|
|
|
plan.is_a?(PartnerPlan)
|
|
|
|
end
|
|
|
|
|
2016-07-08 17:24:51 +02:00
|
|
|
def get_wallet_amount_debit
|
|
|
|
total = plan.amount
|
|
|
|
wallet_amount = (user.wallet.amount * 100).to_i
|
|
|
|
return wallet_amount >= total ? total : wallet_amount
|
|
|
|
end
|
|
|
|
|
|
|
|
def debit_user_wallet
|
|
|
|
if @wallet_amount_debit.present? and @wallet_amount_debit != 0
|
|
|
|
amount = @wallet_amount_debit / 100.0
|
2016-07-20 15:07:43 +02:00
|
|
|
return WalletService.new(user: user, wallet: user.wallet).debit(amount, self)
|
2016-07-08 17:24:51 +02:00
|
|
|
end
|
|
|
|
end
|
2016-03-23 18:39:41 +01:00
|
|
|
end
|