2

I am using Stripe for my subscriptions and I have it set so when a user cancels their subscription (to turn off automatic renewal) it will keep the subscription active until the end of the billing period on Stripe.

The action works via Stripe, but how can I setup so that the cancelled column in my database takes the same affect? Currently if the user clicks on the cancel subscription link it will mark their cancelled column to 1. I would like for it to not mark as cancelled until the end of their billing period so the user can continue have access to the website until their final billing day (I have autorenwal turned on)

I have read txdavidtx suggestion. What he suggests would mark all Users as cancelled at the end of their billing period. That method would not fit with what I am looking to accomplish.

I have subscriptions set to autorenew. I would need a cancel action created that would only mark the current_user as cancelled at the end of their billing period.

For example:

User A signs up for the monthly subscription on September 27. User A decides on December 15 they want to cancel their subscription. User A still has 12 days left in their subscription. User A clicks on the cancel link. User A has autorenew and subscription cancelled in their PayPal or Stripe account. Inside my database their cancelled attribute value will not change until those 12 days have finished (December 27).

If someone can assist that would be great.

Subscriptions controller:

  def new
    plan = Plan.find(params[:plan_id])
    @subscription = plan.subscriptions.build
    render layout: 'new_application'
    if params[:PayerID]
      @subscription.paypal_customer_token = params[:PayerID]
      @subscription.paypal_payment_token = params[:token]
      @subscription.email = @subscription.paypal.checkout_details.email
    end
  end

  def create
    @subscription = Subscription.new(params[:subscription])
    if @subscription.save_with_payment
      redirect_to @subscription, :notice => "Thank you for subscribing!"
    else
      render :new
    end
  end

  def show
    @subscription = Subscription.find(params[:id])
    render layout: 'new_application'
  end

  def paypal_checkout
    plan = Plan.find(params[:plan_id])
    subscription = plan.subscriptions.build
    redirect_to subscription.paypal.checkout_url(
      return_url: new_subscription_url(:plan_id => plan.id),
      cancel_url: root_url
    )
  end

    def updatesubscription
      @user = current_user
      @customer = Stripe::Customer.retrieve(@user.subscription.stripe_customer_token)
      if @user.subscription.plan_id == 12
      @customer.update_subscription(:plan => "1", :prorate => true)
      current_user.subscription.update_attributes(:plan_id => 1)
      flash.alert = 'Your subscription has been changed to monthly!'
      redirect_to root_url
    elsif @user.subscription.plan_id == 1
      @customer.update_subscription(:plan => "12", :prorate => true)
      current_user.subscription.update_attributes(:plan_id => 12)
     current_user.save!
      flash.alert = 'Your subscription has been changed to annually!'
      redirect_to root_url
    end
     end

     def cancelsubscription
       @user = current_user
         @customer = Stripe::Customer.retrieve(@user.subscription.stripe_customer_token)
         @customer.cancel_subscription(:at_period_end => true) 
         current_user.subscription.update_attributes(:cancelled => 1)
         current_user.save!
         flash.alert = 'Your subscription has been cancelled successfully!'
         redirect_to root_url
       end

       def showcard
         @user = current_user
         Stripe::Customer.retrieve(@user.subscription.stripe_customer_token).cards.all()
       end

           def suspend
             @user = current_user
             @user.subscription.suspend_paypal
             current_user.subscription.update_attributes(:cancelled => 1)
               flash.alert = 'Billing has been suspended!'
                redirect_to root_url
           end

           def reactivate
             @user = current_user
             @user.subscription.reactivate_paypal
             current_user.subscription.update_attributes(:cancelled => nil)
               flash.alert = 'Billing has been activated!'
                redirect_to root_url
           end


               def edit_card
                 @user = current_user
               end

               def update_card
                 @user = current_user
                 card_info = {
                   name:    params[:name],
                   number:    params[:number],
                   exp_month: params[:date][:month],
                   exp_year:  params[:date][:year],
                   cvc:       params[:cvc]
                 }
                 if @user.subscription.update_card(@subscriber, card_info)
                   flash.alert = 'Saved. Your card information has been updated.'
                   redirect_to root_url
                 else
                   flash.alert = 'Stripe reported an error while updating your card. Please try again.'
                   redirect_to root_url
                 end
               end
end
Cornelius Wilson
  • 2,844
  • 4
  • 21
  • 41
  • OMG why do you use integer for boolean column? – Mike Szyndel Jul 22 '14 at 20:52
  • When it comes to cancelled I would be rather looking towards something like a state machine (AASM for example), because then you can also track payments (pending, rejected, etc). – Mike Szyndel Jul 22 '14 at 20:53
  • Also I would separate a state (cancelled) from subscription validity. Let's say every month after payment you set a `subscription_expires_at` to `Time.now + 1.month` and if during the month user cancels it sets status to cancelled but the subscription is still valid (because it didn't expire). This should save you some headaches if you make a mistake in code. – Mike Szyndel Jul 22 '14 at 20:56

5 Answers5

8

I think the easiest way is to use stripe webhooks, where as soon as the subscription ends stripe will ping your system with a customer.subscription.deleted event and at that point you should just cancel the user subscription.

The setup is really easy.

  1. You just go to your settings page and add a webhook url and then stripe will start sending you system events.
  2. Then Create a stripe events controller which will parse stripe events in the backend.
  3. Detect the user and cancel his subscription with at_period_end params true

    customer = Stripe::Customer.retrieve("cus_3R1W8PG2DmsmM9")
    customer.subscriptions.retrieve("sub_3R3PlB2YlJe84a").delete(:at_period_end => true
    

A customer.subscription.updated event is immediately triggered if you cancel a subscription at the end of the billing period instead, reflecting the change in the subscription’s cancel_at_period_end value. When the subscription is actually canceled at the end of the period, a customer.subscription.deleted event will occur. Stripe Doc

4- Then Setup your callback controller and detect subscription deleted

<pre>
class StripeEventsController

  skip_before_filter  :verify_authenticity_token

  def catch
    object = params[:data][:object]
    case params[:type]
    when "customer.subscription.deleted"
     customer = object[:id]
     user = User.find_by_stripe_id(customer)
     user.subscription.update_attributes(cancelled: "1")
    end
  end

end

Kimooz
  • 941
  • 9
  • 10
  • This would only allow for the User to cancel. I have that part working already. If you have 15 days left in your subscription, but you cancelled today you would still expect to use your 15 days because you paid for them. That is what I am trying to accomplish. To continue allowing the user to access the website even if they cancelled early. They should be allowed access until their end date. – Cornelius Wilson Oct 28 '14 at 23:12
  • You first cancel the user subscription and add to it "at_period_end" param customer.subscriptions.retrieve("sub_3R3PlB2YlJe84a").delete(:at_period_end => true) A customer.subscription.updated event is immediately triggered if you cancel a subscription at the end of the billing period instead, reflecting the change in the subscription’s cancel_at_period_end value. When the subscription is actually canceled at the end of the period, a customer.subscription.deleted event will occur. – Kimooz Oct 29 '14 at 21:19
  • I'm confused. If Stripe had let's say 3 bad attempts on a card and deletes a subscription, and I receieve customer.subscription.deleted, why would I want to retrieve the customer's subscription and delete it when it's already deleted? Also, wouldn't that create an infinite loop? – Volomike May 04 '15 at 23:06
1

Problem

You need to updated the cancelled column of your user at then end of their trial- not when they actually cancel it.

Solution

Kimooz almost had it right. You're going to use a Stripe Webhook and I'll explain what you need to do in order to achieve what you want.

First, if I understand correctly you want to mimic what happens in Stripe. Basically, you want your database cancelled column to remain false on the user until the user has expired all their time. This means if your user clicks 'Cancel', nothing changes in cancelled column until ALL time has expired, at which point it should then be set to true.

So, cancel your users account with Stripe like you normally would when the user clicks cancel. The only thing different about your users Stripe::Subscription object at this point is that the property cancel_at_period_end gets set to true. The subscription.status property remains active until all the time has expired. Your user record remains the same until ALL time has expired.

Then, set up a Stripe Controller to handle the webhooks. It may seem like a pain to go through this process but this is the proper way to achieve the solution.

IMPORTANT

You need to parse the webhooks at this point for the customer.subscription.updated event NOT the customer.subscription.deleted event. Stripe will only update your customers subscription.

Stripe will not delete your customers subscription if you cancel it

... it will only update the status of the subscription if you cancel it. This means the subscription and the information associated it remains persisted with Stripe and it still belongs to your user if they were to renew it later.

Okay, so.. say your user cancels on Dec. 15, but their subscription ends Dec. 31. Stripe will send two events to your webhook.

  • Dec. 15: This event will be a customer.subscription.updated event that will tell you that cancel_at_period_end property has been updated to true. (the status property remains unchanged and active)

  • Dec. 31.: This event will be a customer.subscription.updated event that will tell you that the status property has changed from active to cancelled.

When the second event is sent your Stripe Webhook, you now can parse the event and change the cancelled status in your own database since all time has expired.

Andrew Sinner
  • 1,841
  • 16
  • 14
0

I would suggest one of two things.

First option:

Add a :cancellation_date attribute then create a helper method in your application controller that checks for the date of cancellation at sign in. If they're over their subscription end date, have your :subscription set itself to "1".

# application_controller.rb
def my_helper
  if current_user.has_cancelled?
    redirect_to registration_path
  end
end

# user.rb although better to have the subscription do it instead
def has_cancelled?
  if cancellation_date.present? && cancellation_date > (paid_on + 30)
    subscription.update_attributes(cancelled: "1")
    return true
  else
    return false
  end
end

Second option:

Use a background job like sucker_punch or sidekiq to run every morning and set expired subscriptions to "1".

dav1dhunt
  • 179
  • 1
  • 10
  • I understand the setup. However that would not address the issue properly. I have it set to auto renew for the monthly & yearly subscription, so I can't setup as you suggested and mark all users as cancelled when their billing period has ended. A user signs up on 9-27, so their cancellation date would be 10-27. But the User never opts out of autorenwal so when 10-27 comes their new end date would be 11-27. That pattern would continue until the user cancels. What I need is to create a action so when a user clicks `cancel` it will mark that specific user as cancelled at the end of their billing. – Cornelius Wilson Oct 27 '14 at 15:18
0

Bad planning IMO:

Your subscription seems to be based on days remaining. So you just need to ensure that days remaining are not 0 when asking for user_is_subscripted.

current_user.subscription_expired?

def subscription_expired?
  days == 0
end

Create a column: subscription days, and each time your user renews the subscription, add 30 days to the count. Each day run a script that reduces the subscription day in that column by 1. This way you don't need to take in account dates.

Also:

if subscription_expired? and autorenewal?
  renew_subscription(days)
end

def renew_subscription(days)
  #paypal magic
  current_user.subscription_days += days
end

And when asking for expiration day:

 def expiration_day
   Time.now + current_user.subscription_days.days
 end
Jorge de los Santos
  • 4,583
  • 1
  • 17
  • 35
  • 1
    I think I will have to take note of Kimooz suggestion and use a web hook. The user is not manually renewing, I have Stripe & PayPal to auto renew by default. So your method would work if my database stayed updated after each auto renewal (but it doesn't). I'm thinking I could add another column `canceling` and have the value change when the user clicks to cancel their subscription. Then do something like this `if cancelled == 2 && cancellation_date > (paid_on + 30) subscription.update_attributes(cancelled: "1")` That may work how I want it. But I would need to learn about web hooks still. – Cornelius Wilson Oct 29 '14 at 00:24
0

The easiest solution to this is, for the method to check if the user still has access, check that the next recurring date is in the future.

This allows you to mark canceled as 1 instantly.

James Chen
  • 10,794
  • 1
  • 41
  • 38