Skip to content

Instantly share code, notes, and snippets.

@schof
Last active December 17, 2015 03:28
Show Gist options
  • Save schof/5542970 to your computer and use it in GitHub Desktop.
Save schof/5542970 to your computer and use it in GitHub Desktop.
Basic logic for recurring subscriptions. NOTE: Uses older version of Spree so it needs some tweaks for Spree 2.0
class CreateSubscriptions < ActiveRecord::Migration
def self.up
create_table :subscriptions do |t|
t.date :start_date
t.date :end_date
t.integer :duration
t.string :interval
t.string :state
t.references :user
t.references :variant
t.timestamps
end
end
def self.down
drop_table :subscriptions
end
end
class AddSubcriptionIdToPayments < ActiveRecord::Migration
def self.up
add_column :payments, :subscription_id, :integer
end
def self.down
remove_column :payments, :subscription_id
end
end
class AddFieldsToVariant < ActiveRecord::Migration
def self.up
add_column :variants, :subscribable, :boolean, :default => false
end
def self.down
remove_column :variants, :subscribable
end
end
class AddCcToSubscription < ActiveRecord::Migration
def self.up
add_column :subscriptions, :creditcard_id, :integer
end
def self.down
remove_column :subscriptions, :creditcard_id
end
end
class CreateExpiryNotifications < ActiveRecord::Migration
def self.up
create_table :expiry_notifications do |t|
t.integer :subscription_id
t.integer :interval
t.timestamps
end
end
def self.down
drop_table :expiry_notifications
end
end
class AddPaymentProfileKeyToSubscription < ActiveRecord::Migration
def self.up
add_column :subscriptions, :payment_profile_key, :string
end
def self.down
remove_column :subscriptions, :payment_profile_key
end
end
class ExpiryNotification < ActiveRecord::Base
belongs_to :subscription
end
class Subscription < ActiveRecord::Base
belongs_to :user
belongs_to :variant
belongs_to :creditcard
has_many :payments, :dependent => :destroy, :order => :created_at
has_many :expiry_notifications
before_save :set_dates
state_machine :state, :initial => 'active' do
event :cancel do
transition :to => 'canceled', :if => :allow_cancel?
end
event :expire do
transition :to => 'expired', :from => 'active'
end
event :reactivate do
transition :to => 'active', :from => 'expired'
end
end
def allow_cancel?
self.state != 'canceled'
end
def due_on
self.active? ? self.start_date + eval(self.duration.to_s + "." + self.interval.to_s) : nil
end
def renew
set_dates
end
private
def set_dates
self.start_date = Time.now if self.start_date.nil?
self.end_date = Time.now + eval(self.duration.to_s + "." + self.interval.to_s)
end
end
class SubscriptionMailer < ActionMailer::QueueMailer
helper "spree/base"
def paymenet_receipt(subscription)
@subject = Spree::Config[:site_name] + ' ' + 'Subscription Renewal #' + subscription.id.to_s
@body = {"subscription" => subscription}
@recipients = subscription.user.email
@from = Spree::Config[:order_from]
@sent_on = Time.now
end
def expiry_warning(subscription, within)
@subject = Spree::Config[:site_name] + ' ' + 'Creditcard for Subscription #' + subscription.id.to_s + ' is due to expire'
@body = {"subscription" => subscription, "within" => within}
@recipients = subscription.user.email
@from = Spree::Config[:order_from]
@sent_on = Time.now
end
def creditcard_expired(subscription)
@subject = Spree::Config[:site_name] + ' ' + 'Creditcard for Subscription #' + subscription.id.to_s + ' has expired'
@body = {"subscription" => subscription}
@recipients = subscription.user.email
@from = Spree::Config[:order_from]
@sent_on = Time.now
end
def subscription_reactivated(subscription)
@subject = Spree::Config[:site_name] + ' ' + 'Subscription #' + subscription.id.to_s + ' has been reactivated'
@body = {"subscription" => subscription}
@recipients = subscription.user.email
@from = Spree::Config[:order_from]
@sent_on = Time.now
end
end
include ActionView::Helpers::DateHelper
class SubscriptionManager
def SubscriptionManager.process
subscriptions = Subscription.find(:all, :conditions => {:state => 'active'})
check_for_renewals(subscriptions)
check_for_creditcard_expiry(subscriptions)
end
def SubscriptionManager.check_for_renewals(subscriptions)
subscriptions.each do |sub|
next unless sub.due_on.to_time <= Time.now()
#subscription due for renewal
#re-curring payment
amount = sub.variant.price * 100
gateway = Gateway.find(:first, :conditions => {:active => true, :environment => ENV['RAILS_ENV']})
response = gateway.purchase(amount, sub.payment_profile_key)
puts response.to_yaml
if response.success?
payment = CreditcardPayment.create(:subscription => sub, :amount => sub.variant.price, :type => "CreditcardPayment", :creditcard => sub.creditcard)
payment.creditcard_txns << CreditcardTxn.new(
:amount => amount,
:response_code => response.authorization,
:txn_type => CreditcardTxn::TxnType::PURCHASE
)
subscription.payments << payment
SubscriptionMailer.deliver_paymenet_receipt(sub)
end
end
end
def SubscriptionManager.check_for_creditcard_expiry(subscriptions)
subscriptions.each do |sub|
next unless sub.creditcard.expiry_date.expiration < (Time.now + 3.months)
#checks for credit cards due to expiry with all the following ranges
[1.day, 3.days, 1.week, 2.weeks, 3.weeks, 1.month, 2.months, 3.months].each do |interval|
within = distance_of_time_in_words(Time.now, Time.now + interval)
if sub.creditcard.expiry_date.expiration.to_time < (Time.now + interval) && sub.end_date.to_time > (Time.now + interval)
unless ExpiryNotification.exists?(:subscription_id => sub.id, :interval => interval.seconds.to_i)
notification = ExpiryNotification.create(:subscription_id => sub.id, :interval => interval.seconds)
SubscriptionMailer.deliver_expiry_warning(sub, within)
end
break
end
end
#final check if credit card has actually expired
if sub.creditcard.expiry_date.expiration < Time.now
sub.expire
SubscriptionMailer.deliver_creditcard_expired(sub)
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment