Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Topher/add subscription options #3

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,13 @@ You'll also need to make this attribute mass-assignable:
include Koudoku::Plan
attr_accessible :interval
end

## Version 0.0.16

Fixed updating credit card as default. There was issue Accounts that are Past due. CC, cannot be updated.

1. User creates a CF account and makes a valid payment.
2. After x months' user's CC goes out of date or is declined for another reason.
3. User loses access to their funnel dashboard.
4. When user logs back in, they are presented with a choice to choose a new plan.
5. They enter new CC info and select a plan.
152 changes: 96 additions & 56 deletions app/concerns/koudoku/subscription.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ module Koudoku::Subscription
# We don't store these one-time use tokens, but this is what Stripe provides
# client-side after storing the credit card information.
attr_accessor :credit_card_token

attr_accessor :skip_prorate_plan_changes

belongs_to :plan

# update details.
before_save :processing!
def processing!
# update details. Note: to prevent recursive callbacks on the processing method, this callback
# can be skipped by setting @skip_proccessing_callback
before_save :processing!, unless: lambda { @skip_proccessing_callback == true }

def processing!
# if their package level has changed ..
if changing_plans?
if changing_plans?

prepare_for_plan_change

Expand All @@ -26,30 +28,42 @@ def processing!

# if a new plan has been selected
if self.plan.present?

# Record the new plan pricing.
self.current_price = self.plan.price

prepare_for_downgrade if downgrading?
prepare_for_upgrade if upgrading?

sub = customer.subscriptions.first
if sub && sub.trial_end && sub.trial_end > Time.now.to_i
trial_end = sub.trial_end
# update package level and adjust trial end to match current subscription trial_end + add starting plan trial
stripe_plan = Stripe::Plan.retrieve(self.plan.stripe_id)
if stripe_plan.trial_period_days
trial_end = trial_end + stripe_plan.trial_period_days.to_i.days
begin
# Record the new plan pricing.
self.current_price = self.plan.price

prepare_for_downgrade if downgrading?
prepare_for_upgrade if upgrading?

# updating a default credit card
update_default_stripe_card

sub = customer.subscriptions.first
if sub && sub.trial_end && sub.trial_end > Time.now.to_i
trial_end = sub.trial_end
# update package level and adjust trial end to match current subscription trial_end + add starting plan trial
stripe_plan = Stripe::Plan.retrieve(self.plan.stripe_id)
if stripe_plan.trial_period_days
trial_end = trial_end + stripe_plan.trial_period_days.to_i.days
end
opts = { plan: self.plan.stripe_id, trial_end: trial_end }
opts = subscription_options(opts)
customer.update_subscription(opts) if Koudoku.keep_trial_end
else
# update the package level with stripe.
opts = {plan: self.plan.stripe_id}
opts[:prorate] = false if skip_prorate_plan_changes
opts = subscription_options(opts)
customer.update_subscription(opts)
end
customer.update_subscription(:plan => self.plan.stripe_id, trial_end: trial_end) if Koudoku.keep_trial_end
else
# update the package level with stripe.
customer.update_subscription(:plan => self.plan.stripe_id)
end

finalize_downgrade! if downgrading?
finalize_upgrade! if upgrading?

finalize_downgrade! if downgrading?
finalize_upgrade! if upgrading?
rescue Stripe::CardError => card_error
errors[:base] << card_error.message
card_was_declined
return false
end
# if no plan has been selected.
else

Expand Down Expand Up @@ -86,8 +100,7 @@ def processing!
customer_attributes = {
description: subscription_owner_description,
email: subscription_owner_email,
card: credit_card_token, # obtained with Stripe.js
plan: plan.stripe_id
card: credit_card_token # obtained with Stripe.js
}

# If the class we're being included in supports coupons ..
Expand All @@ -97,19 +110,28 @@ def processing!
end
end

# create a customer at that package level.
# create a customer without the plan to start
customer = Stripe::Customer.create(customer_attributes)


# Store the stripe customer id in our db.
# We do not want this save to trigger the 'processing' method again so force that
# callback to skip
@skip_proccessing_callback = true
self.update_attributes( {
stripe_id: customer.id,
last_four: customer.cards.retrieve(customer.default_card).last4
} )
@skip_proccessing_callback = false

# now that we have recorded the stripe_id in our system we can setup the subscription in stripe
customer.plan = plan.stripe_id
customer.save
rescue Stripe::CardError => card_error
errors[:base] << card_error.message
card_was_declined
return false
end

# store the customer id.
self.stripe_id = customer.id
self.last_four = customer.cards.retrieve(customer.default_card).last4

finalize_new_subscription!
finalize_upgrade!

Expand All @@ -127,21 +149,10 @@ def processing!
end

finalize_plan_change!

# if they're updating their credit card details.
elsif self.credit_card_token.present?

prepare_for_card_update

# fetch the customer.
customer = Stripe::Customer.retrieve(self.stripe_id)
customer.card = self.credit_card_token
customer.save

# update the last four based on this new card.
self.last_four = customer.cards.retrieve(customer.default_card).last4
finalize_card_update!

update_default_stripe_card
end

end
Expand All @@ -158,7 +169,7 @@ def describe_difference(plan_to_describe)
else
if Koudoku.free_trial?
"Start Trial"
else
else
"Upgrade"
end
end
Expand All @@ -171,6 +182,30 @@ def describe_difference(plan_to_describe)
end
end

#
# Updates the default credit card
#
def update_default_stripe_card
return false unless credit_card_token.present?

prepare_for_card_update

# fetch the customer.
customer = Stripe::Customer.retrieve(self.stripe_id)
source = customer.sources.create(source: credit_card_token)
customer.default_source = source.id
customer.save

# update the last four based on this new card.
self.last_four = customer.cards.retrieve(customer.default_card).last4

finalize_card_update!
rescue Stripe::CardError => card_error
errors[:base] << card_error.message
card_was_declined
return false
end

# Pretty sure this wouldn't conflict with anything someone would put in their model
def subscription_owner
# Return whatever we belong to.
Expand All @@ -189,15 +224,20 @@ def subscription_owner_email
end

def changing_plans?
plan_id_changed?
will_save_change_to_plan_id?
end

def downgrading?
plan.present? and plan_id_was.present? and plan_id_was > self.plan_id
plan.present? and plan_id_before_last_save.present? and plan_id_before_last_save > self.plan_id
end

def upgrading?
(plan_id_was.present? and plan_id_was < plan_id) or plan_id_was.nil?
(plan_id_before_last_save.present? and plan_id_before_last_save < plan_id) or plan_id_before_last_save.nil?
end

# CF Template methods.
def subscription_options(opts = {})
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the relevant new method. I'm not sure what the other stuff is or why it's showing up in this PR. I guess those changes haven't been merged to master? Or maybe they were reverted?

opts
end

# Template methods.
Expand All @@ -215,7 +255,7 @@ def prepare_for_downgrade

def prepare_for_cancelation
end

def prepare_for_card_update
end

Expand All @@ -239,14 +279,14 @@ def finalize_card_update!

def card_was_declined
end

# stripe web-hook callbacks.
def payment_succeeded(amount)
end

def charge_failed
end

def charge_disputed
end

Expand Down
2 changes: 1 addition & 1 deletion lib/koudoku/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Koudoku
VERSION = "0.0.15"
VERSION = "0.0.16"
end