RSS

Newsletter

The CodeKarate Newsletter is the best way for technical ninjas to keep their swords sharpened. Don't worry, we won't flood your inbox and your email address will always remain private.
Back to Top

Recurly subscription integration in Django

In my last post I discussed setting up a Payment Form in Django for entering credit card information. Here is how I tied it in on the back-end to integrate with Recurly for the recurring payments on http://getpropeller.com.

I added the following to a file called subscriptions.py and added it to one of my Django Apps. It provides a light wrapper around the Recurly Python library.

class SubscriptionManager:
  """
  A class that handles the subscriptions on Propeller
  """
  def __init__(self, company):
    self.company = company
    recurly.API_KEY = settings.RECURLY_API_KEY
    recurly.DEFAULT_CURRENCY = settings.RECURLY_DEFAULT_CURRENCY
 
    # Get the coupon amount from their company information
    if self.company.coupon_id:
      coupon = CouponCode.objects.get(id=self.company.coupon_id)
      self.coupon_amount = coupon.amount
    else:
      self.coupon_amount = 1
 
  def create(self, plan, payment_details):
    """
    Created a new recurly subscription.
    """
    subscription = recurly.Subscription()    
    subscription.plan_code = self._get_plan_code(plan)
 
    trial_end = self.company.created + datetime.timedelta(days=(self.coupon_amount * 30))
    if datetime.datetime.now() < trial_end:
      subscription.trial_ends_at = trial_end
 
    account = recurly.Account(account_code=self.company.id)
    account.email = self.company.user_id.email
    account.first_name = payment_details.cleaned_data.get('first_name')
    account.last_name = payment_details.cleaned_data.get('last_name')
 
    billing_info = recurly.BillingInfo()
    billing_info.number = payment_details.cleaned_data.get('number')
    billing_info.month = int(payment_details.cleaned_data.get('expire_month'))
    billing_info.year = int(payment_details.cleaned_data.get('expire_year'))
 
    account.billing_info = billing_info
    subscription.account = account
 
    subscription.save()
 
    # Update the company information and flush the company cache
    self.company.active_subscription = subscription.uuid
    self.company.save()
 
  def downgrade(self, plan):
    """
    Downgrades a recurly subscrption. This can be used to downgrade from one paying
    account to another, in which case the subscription is updated, or to downgrade
    to a free account, in which case the subscription is cancelled
    """
    # Determine if we are downgrading to another paying account, if so, update subscription
    if plan > 0:
      self._update_subscription(plan)
    else: # If we are downgrading to free, cancel subscription
      self._cancel_subscription()
 
  def upgrade(self, plan, payment_details=None):
    """
    Upgrades a recurly subscription. This can be used to upgrade from a free account,
    in which case a new subscription is created, or from an existing account, in
    which case the subscription is updated
    """
 
    # Check if this is a new subscription
    if not self.company.active_subscription or self.company.active_subscription == "":
      return self.create(plan, payment_details)
    else: #Its an update to an existing subscription
      return self._update_subscription(plan)
 
  def update(self, payment_details=None):
    """
    Updates credit card information on Recurly
    """
    account = self.account()
    billing_info = account.billing_info
    billing_info.first_name = payment_details.cleaned_data.get('first_name')
    billing_info.last_name = payment_details.cleaned_data.get('last_name')
    billing_info.verification_value = payment_details.cleaned_data.get('cvv_number')
    billing_info.number = payment_details.cleaned_data.get('number')
    billing_info.month = int(payment_details.cleaned_data.get('expire_month'))
    billing_info.year = int(payment_details.cleaned_data.get('expire_year'))
 
    try:
      billing_info.save()
      return True
    except recurly.ValidationError:
      return False
 
  def reactivate(self):
    if self.company.active_subscription:
      try:
        subscription = recurly.Subscription.get(self.company.active_subscription)
        subscription.reactivate()
        return True
      except:
        pass
 
    return False
 
  def payments(self):
    """
    Gets a list of all payments (Recurly calls them invoices) that a user has for
    their account
    """
    try:
      account = recurly.Account.get(self.company.id)
      return account.invoices()
    except recurly.NotFoundError:
      return False
 
  def subscription(self):
    """
    Gets a users current subscription
    """
    if self.company.active_subscription:
      try:
        return recurly.Subscription.get(self.company.active_subscription)
      except:
        pass
 
    return False
 
  def account(self):
    """
    Gets a users current account information from Recurly
    """
    try:
      return recurly.Account.get(self.company.id)
    except recurly.NotFoundError:
      return False
 
  def invoice(self, invoice_id):
    """
    Tries to get an invoice and determines if it is available for the current company
    """
    try:
      invoice = recurly.Invoice.get(invoice_id)
      return invoice
      if invoice.uuid == self.company.active_subscription:
        return invoice
    except:
      pass
 
    return False
 
  def _update_subscription(self, plan):
    """
    Updates an existing recurly subscription
    """
    if self.company.active_subscription:
      try:
        subscription = recurly.Subscription.get(self.company.active_subscription)
      except:
        pass
      else:
        subscription.plan_code = self._get_plan_code(plan)
 
        if self.company.plan > plan:
          #this is an upgrade so process now
          subscription.timeframe = 'now'       # Update immediately.
        else: #this is a downgrade, do it at renewal time
          subscription.timeframe = 'renewal' # Update when the subscription renews.
 
        try:  
          subscription.save()
          return True
        except:
          pass
 
    return False
 
  def _cancel_subscription(self):
    """
    Cancels an existing recurly subscription
    """
    if self.company.active_subscription:
      try:
        subscription = recurly.Subscription.get(self.company.active_subscription)
        subscription.cancel()
        return True
      except:
        pass
 
    return False
 
  def _get_plan_code(self, plan):
    """
    Gets the coupon amount (free month count)
    """
    plan_code = 'plan-one'
    if plan == 2:
      plan_code = 'plan-two'
 
    return plan_code

The class above can then be easily called to create, upgrade, downgrade, or cancel between different plan levels. There are still things I need to add to finish the process (such as processing the push notifications from Recurly when the cancellation actually goes through, but I will leave that for a future post). This is related to my post called Django dynamically retrieve and serve PDF file, so you will see some similar code.

Discussions

Post new comment

  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <h2> <h3> <blockquote> <img>
  • Lines and paragraphs break automatically.
  • You can enable syntax highlighting of source code with the following tags: <css>, <html>, <php>, <c>, <cpp>, <drupal5>, <drupal6>, <java>, <javascript>, <mysql>, <python>, <ruby>. PHP source code can also be enclosed in <?php ... ?> or <% ... %>.

More information about formatting options

By submitting this form, you accept the Mollom privacy policy.