<commit_before>xml2js = require 'xml2js'
https = require 'https'

createId = require 'hat'
getUniqueId = -> createId 64

{argv} = require 'optimist'
KONFIG = require('koding-config-manager').load("main.#{argv.c}")

API_KEY = KONFIG.recurly.apiKey

#
# Recurly's error entry structure. I'll use the same structure 
# for socket/http errors.
#
Error = (short, message, field) ->
  err =
    short   : short
    message : message
    field   : field
  return err

summarizeErrors = (errors)->
  billing = 0
  for e in errors
    if e.field.indexOf('subscription.account.billing_info') > -1
      billing++
  if billing > 2
    return Error('billing', 'Credit card information required.', '')

#
# If requested list of records are longer than 50 items, recurly
# paginates them. This method gets the URL path of next page.
#
parseRecurlyLink = (res)->
  links = {}
  for link in res.headers.link.split(', ')
    linkData = link.split('; ')
    rel = linkData[1].substring(5, linkData[1].length - 1)
    url = linkData[0].substring(1, linkData[0].length - 1)
    links[rel] = url.substring(url.indexOf('v2') + 3)
  return links

#
# This method makes a request to Recurly. If the page is chunked,
# it waits for the last chunk and returns output as JS object.
#
recurlyRequest = (method, operation, data, callback) ->
  options =
    host     : 'api.recurly.com'
    port     : 443
    path     : '/v2/' + operation
    method   : method
    auth     : API_KEY + ':'
    headers  :
      accept           : 'application/xml'
      'content-length' : data.length

  if data.length > 0
    options.headers['content-type'] = 'application/xml; charset=utf-8'

  req = https.request options, (res) ->
    res.setEncoding 'utf8'
    body = ''
    res.on 'data', (chunk) ->
      body += chunk
    res.on 'end', () ->
      # Find next page's URL if necessary
      if res.headers.link
        nextUrl = parseRecurlyLink(res).next
      # Parse XML and build JS object, or list of errors.
      if body.length > 0
        xml2js.parseString body, (e, r) ->
          if e
            errors = [Error('xml', 'Invalid XML', '')]
            callback errors, {}
          else if r.error
            errors = [Error(r.error.symbol[0], r.error.description[0]['_'], '')]
            callback errors, {}
          else if r.errors
            # Build list of errors
            # See https://docs.recurly.com/api/basics/validation-errors for XML format
            errors = []
            for err in r.errors.error
              errors.push Error(err['$']['symbol'], err['_'], err['$']['field'])
            callback errors, {}
          else
            callback no, r, nextUrl
      else
        callback no, {}

  req.on 'error', (e) ->
    errors = [Error('connection', 'Unable to connect to payment backend.' , '')]
    callback errors, {}

  if data.length > 0
    req.write data
  req.end()

# POST request
recurlyPost = (operation, data, callback) ->
  recurlyRequest 'POST', operation, data, (e, r) ->
    callback e, r

# GET request.
# If there are multiple pages, this method requests all pages.
# Returns list of XML/JS objects, not a single XML/JS object!
recurlyGet = (operation, callback) ->
  if operation.indexOf('per_page') == -1
    if operation.indexOf('?') == -1
      operation += '?per_page=50'
    else
      operation += '&per_page=50'

  # Collect all GET responses before calling CB
  allData = []
  collect = (e, r, n)->
    if e
      callback e, []
    else
      allData.push r
      unless n
        callback no, allData
        allData = []
      else
        recurlyRequest 'GET', n, '', collect

  recurlyRequest 'GET', operation, '', collect

# PUT request
recurlyPut = (operation, data, callback) ->
  recurlyRequest 'PUT', operation, data, (e, r) ->
    callback e, r

# DELETE request
recurlyDel = (operation, callback) ->
  recurlyRequest 'DELETE', operation, '', (e, r) ->
    callback e, r

# Returns list of plans on recurly
getPlans = (callback) ->
  recurlyGet 'plans', (err, res) ->
    if err
      callback err, {}
    else
      plans = []
      for page in res
        # See https://docs.recurly.com/api/plans for XML format
        if page.plans?.plan
          for p in page.plans.plan
            plans.push
              title      : p.name[0]
              code       : p.plan_code[0]
              desc       : if typeof p.description[0] is "string" then p.description[0] else ""
              feeMonthly : parseInt p.unit_amount_in_cents[0]['USD'][0]['_']
              feeInitial : parseInt p.setup_fee_in_cents[0]['USD'][0]['_']
      callback no, plans

getPlanInfo = (data, callback)->
  recurlyGet "plans/#{data.code}", (err, res) ->
    if err
      callback err, {}
    else
      plan = []
      for page in res
        # See https://docs.recurly.com/api/plans for XML format
        if page.plan
          plan =
            code       : page.plan.plan_code[0]
            title      : page.plan.name[0]
            desc       : if typeof page.plan.description[0] is "string" then page.plan.description[0] else ""
            feeMonthly : parseInt page.plan.unit_amount_in_cents[0]['USD'][0]['_']
            feeInitial : parseInt page.plan.setup_fee_in_cents[0]['USD'][0]['_']
          callback no, plan
        else
          callback yes, {}
        break

# # This method is for user sellables.
# getUserPlans = (username, callback) ->
#   recurlyGet 'plans', (err, res) ->
#     if err
#       callback err, {}
#     else
#       plans = []
#       for page in res
#         # See https://docs.recurly.com/api/plans for XML format
#         if page.plans?.plan
#           for p in page.plans.plan
#             if p.plan_code[0].indexOf("user_#{username}_") > -1
#               plans.push
#                 title  : p.name[0]
#                 name   : p.plan_code[0]
#                 desc   : if p.description[0].$ then "" else p.description[0]
#                 amount : parseInt p.unit_amount_in_cents[0]['USD'][0]['_']
#                 initial: parseInt p.setup_fee_in_cents[0]['USD'][0]['_']
#       callback no, plans

addPlan = (data, callback) ->
  # See https://docs.recurly.com/api/plans for XML format
  data.feeInitial ?= 0 
  xml = """<plan>
             <plan_code>#{data.code}</plan_code>
             <name>#{data.title}</name>
             <setup_fee_in_cents>
               <USD>#{parseInt(data.feeInitial)}</USD>
             </setup_fee_in_cents>
             <unit_amount_in_cents>
               <USD>#{parseInt(data.feeMonthly)}</USD>
             </unit_amount_in_cents>
             <plan_interval_length>1</plan_interval_length>
             <plan_interval_unit>months</plan_interval_unit>
           </plan>"""

  recurlyPost 'plans', xml, (err, res) ->
    if err
      callback err, {}
    else
      plan =
        code       : res.plan.plan_code[0]
        title      : res.plan.name[0]
        desc       : if typeof res.plan.description[0] is "string" then res.plan.description[0] else ""
        feeMonthly : parseInt res.plan.unit_amount_in_cents[0]['USD'][0]['_']
        feeInitial : parseInt res.plan.setup_fee_in_cents[0]['USD'][0]['_']
      callback no, plan

deletePlan = (data, callback) ->
  recurlyDel "plans/#{data.code}", (err, res) ->
    if err
      callback err, {}
    else
      callback no, res

getUserSubscriptions = (accountCode, callback) ->
  recurlyGet "accounts/#{accountCode}/subscriptions", (err, res) ->
    if err
      callback err, []
    else
      plans = []
      for page in res
        # See https://docs.recurly.com/api/subscriptions for XML format
        if page.subscriptions?.subscription
          for s in page.subscriptions.subscription
            if s.state[0] not in ['expired', 'failed']
              plans.push
                plan     : s.plan[0].plan_code[0]
                quantity : s.quantity[0]['_']
                uuid     : s.uuid[0]
                status   : s.state[0]
                datetime : s.activated_at[0]['_']
                expires  : s.expires_at[0]['_']
                renew    : s.current_period_ends_at[0]['_']
      callback no, plans

getSubscriptionInfo = (accountCode, data, callback) ->
  recurlyGet "subscriptions/#{data.uuid}", (err, res) ->
    if err
      callback err, []
    else
      for page in res
        # See https://docs.recurly.com/api/subscriptions for XML format
        if page.subscription
          plan =
            plan     : page.subscription.plan[0].plan_code[0]
            quantity : page.subscription.quantity[0]['_']
            uuid     : page.subscription.uuid[0]
            status   : page.subscription.state[0]
            datetime : page.subscription.activated_at[0]['_']
            expires  : page.subscription.expires_at[0]['_']
            renew    : page.subscription.current_period_ends_at[0]['_']
          callback no, plan
        else
          callback yes, {}

addUserSubscription = (accountCode, data, callback) ->
  data.quantity ?= 1

  # See https://docs.recurly.com/api/subscriptions for XML format
  xml = """<?xml version="1.0" encoding="UTF-8"?>
            <subscription>
              <plan_code>#{data.plan}</plan_code>
              <currency>USD</currency>
              <quantity>#{data.quantity}</quantity>
              <account>
                <account_code>#{accountCode}</account_code>
              </account>
            </subscription>"""

  recurlyPost 'subscriptions', xml, (err, res) ->
    if err
      callback err, {}
    else
      subs =
        uuid       : res.subscription.uuid[0]
        plan       : res.subscription.plan[0].plan_code[0]
        quantity   : res.subscription.quantity[0]['_']
        status     : res.subscription.state[0]
        currency   : res.subscription.currency[0]
        datetime   : res.subscription.activated_at[0]['_']
        expires    : res.subscription.expires_at[0]['_']
        renew      : res.subscription.current_period_ends_at[0]['_']
      callback no, subs

updateUserSubscription = (accountCode, data, callback) ->
  data.quantity ?= 1

  # See https://docs.recurly.com/api/subscriptions for XML format
  xml = """<?xml version="1.0" encoding="UTF-8"?>
           <subscription>
             <timeframe>now</timeframe>
             <plan_code>#{data.plan}</plan_code>
             <quantity>#{data.quantity}</quantity>
           </subscription>"""

  recurlyPut "subscriptions/#{data.uuid}", xml, (err, res) ->
    if err
      callback err, {}
    else
      subs =
        uuid       : res.subscription.uuid[0]
        plan       : res.subscription.plan[0].plan_code[0]
        quantity   : res.subscription.quantity[0]['_']
        status     : res.subscription.state[0]
        currency   : res.subscription.currency[0]
        datetime   : res.subscription.activated_at[0]['_']
        expires    : res.subscription.expires_at[0]['_']
        renew      : res.subscription.current_period_ends_at[0]['_']
      callback no, subs

cancelUserSubscription = (accountCode, data, callback) ->
  recurlyPut "subscriptions/#{data.uuid}/cancel", "", (err, res) ->
    if err
      callback err, {}
    else
      subs =
        uuid       : res.subscription.uuid[0]
        plan       : res.subscription.plan[0].plan_code[0]
        quantity   : res.subscription.quantity[0]['_']
        status     : res.subscription.state[0]
        currency   : res.subscription.currency[0]
        datetime   : res.subscription.activated_at[0]['_']
        expires    : res.subscription.expires_at[0]['_']
        renew      : res.subscription.current_period_ends_at[0]['_']
      callback no, subs

reactivateUserSubscription = (accountCode, data, callback) ->
  recurlyPut "subscriptions/#{data.uuid}/reactivate", "", (err, res) ->
    if err
      callback err, {}
    else
      subs =
        uuid       : res.subscription.uuid[0]
        plan       : res.subscription.plan[0].plan_code[0]
        quantity   : res.subscription.quantity[0]['_']
        status     : res.subscription.state[0]
        currency   : res.subscription.currency[0]
        datetime   : res.subscription.activated_at[0]['_']
        expires    : res.subscription.expires_at[0]['_']
        renew      : res.subscription.current_period_ends_at[0]['_']
      callback no, subs
      
getUserTransactions = (accountCode, callback) ->
  recurlyGet "accounts/#{accountCode}/transactions", (err, res) ->
    if err
      callback err, []
    else
      transactions = []
      for page in res
        # See https://docs.recurly.com/api/transactions for XML format
        if page.transactions?.transaction
          for t in page.transactions.transaction
            account = t.details[0].account[0]
            billing = t.details[0].account[0].billing_info[0]
            transactions.push
              uuid      : t.uuid[0]
              status    : t.status[0]
              amount    : parseInt t.amount_in_cents[0]['_']
              tax       : parseInt t.tax_in_cents[0]['_']
              currency  : t.currency[0]
              datetime  : t.created_at[0]['_']
              refundable: t.refundable[0]['_']
              owner     : "#{billing.first_name[0]} #{billing.last_name[0]}"
              card      : "#{billing.card_type[0]} - #{billing.first_six.toString().substring(0,4)}-...-#{billing.last_four}"
              source    : t.source[0]
      callback no, transactions

getUserInvoices = (accountCode, callback)->
   recurlyGet "accounts/#{accountCode}/invoices", (err, res) ->
    if err
      callback err, []
    else
      invoices = []
      for page in res
        # See https://docs.recurly.com/api/invoices for XML format
        if page.invoices?.invoice
          for t in page.invoices.invoice
            invoices.push
              uuid      : t.uuid[0]
              amount    : t.total_in_cents[0]['_']
              currency  : t.currency[0]
              state     : t.state[0]
      callback no, invoices

getUserAdjustments = (accountCode, type, callback)->
   recurlyGet "accounts/#{accountCode}/adjustments?state=" + type, (err, res) ->
    if err
      callback err, []
    else
      adjustments = []
      for page in res
        # See https://docs.recurly.com/api/adjustments for XML format
        if page.adjustments?.adjustment
          for t in page.adjustments.adjustment
            adjustments.push
              uuid        : t.uuid[0]
              amount      : t.total_in_cents[0]['_']
              currency    : t.currency[0]
              description : if t.description[0].$ then "" else t.description[0]
              origin      : t.origin[0]
      callback no, adjustments

getUserCharged = (accountCode, callback)->
  getUserAdjustments accountCode, 'invoiced', callback

getUserPending = (accountCode, callback)->
  getUserAdjustments accountCode, 'pending', callback

chargeUserPending = (accountCode, callback)->
  recurlyPost "accounts/#{accountCode}/invoices", '', (err, res) ->
    if err
      callback err, {}
    else
      if res.invoice
        invoice = 
          uuid     : res.invoice.uuid[0]
          amount   : res.invoice.total_in_cents[0]['_']
          currency : res.invoice.currency[0]
        callback no, invoice
      else
        callback no, {}

addUserCharge = (accountCode, data, callback)->
  # See https://docs.recurly.com/api/adjustments for XML format
  xml = """<adjustment>
             <description>#{data.desc}</description>
             <unit_amount_in_cents>#{data.amount}</unit_amount_in_cents>
             <currency>USD</currency>
             <quantity>1</quantity>
             <accounting_code>#{data.accountingCode}</accounting_code>
           </adjustment>"""

  recurlyPost "accounts/#{accountCode}/adjustments", xml, (err, res)->
    if err
      callback err, {}
    else
      charge =
        uuid     : res.adjustment.uuid[0]
        amount   : res.adjustment.total_in_cents[0]['_']
        currency : res.adjustment.currency[0]
      callback no, charge

getBilling = (accountCode, callback) ->
  recurlyGet "accounts/#{accountCode}/billing_info", (err, res) ->
    if err
      callback err, {}
    else
      for page in res
        # See https://docs.recurly.com/api/billing-info for XML format
        if page.billing_info
          info = page.billing_info
          billing =
            cardFirstName     : info.first_name[0]
            cardLastName      : info.last_name[0]
            cardNumber        : info.first_six.toString().substring(0,4) + '-...-' + info.last_four
            cardMonth         : info.month[0]['_']
            cardYear          : info.year[0]['_']
            cardCV            : 'XXX'
            cardType          : info.card_type[0]
            address1          : info.address1[0]
            address2          : info.address2[0]
            city              : info.city[0]
            state             : info.state[0]
            zip               : info.zip[0]
            country           : info.country[0]
        else
          billing = {}
      callback no, billing

getAccount = (accountCode, callback) ->
  recurlyGet "accounts/#{accountCode}", (err, res) ->
    if err
      callback err, {}
    else
      for page in res
        # See https://docs.recurly.com/api/account for XML format
        if page.account
          account =
            firstName : page.account.first_name[0]
            lastName  : page.account.last_name[0]
            email     : page.account.email[0]
            username  : page.account.username[0]
            url       : 'https://koding.recurly.com/account/' + page.account.hosted_login_token[0]
          getBilling accountCode, (e, r) ->
            unless e
              for k, v of r
                account[k] = v
            callback no, account
        else
          callback no, {}

deleteAccount = (accountCode, callback) ->
  recurlyDel "accounts/#{accountCode}/billing_info", (err, res) ->
    if err
      callback err, {}
    else
      callback no, res

setAccount = (accountCode, data, callback) ->
  # See https://docs.recurly.com/api/account for XML format
  xml = """<?xml version="1.0" encoding="UTF-8"?>
           <account>
             <account_code>#{accountCode}</account_code><commit_msg>Filter credit card number and security code if it's not updated<commit_after>xml2js = require 'xml2js'
https = require 'https'

createId = require 'hat'
getUniqueId = -> createId 64

{argv} = require 'optimist'
KONFIG = require('koding-config-manager').load("main.#{argv.c}")

API_KEY = KONFIG.recurly.apiKey

#
# Recurly's error entry structure. I'll use the same structure 
# for socket/http errors.
#
Error = (short, message, field) ->
  err =
    short   : short
    message : message
    field   : field
  return err

summarizeErrors = (errors)->
  billing = 0
  for e in errors
    if e.field.indexOf('subscription.account.billing_info') > -1
      billing++
  if billing > 2
    return Error('billing', 'Credit card information required.', '')

#
# If requested list of records are longer than 50 items, recurly
# paginates them. This method gets the URL path of next page.
#
parseRecurlyLink = (res)->
  links = {}
  for link in res.headers.link.split(', ')
    linkData = link.split('; ')
    rel = linkData[1].substring(5, linkData[1].length - 1)
    url = linkData[0].substring(1, linkData[0].length - 1)
    links[rel] = url.substring(url.indexOf('v2') + 3)
  return links

#
# This method makes a request to Recurly. If the page is chunked,
# it waits for the last chunk and returns output as JS object.
#
recurlyRequest = (method, operation, data, callback) ->
  options =
    host     : 'api.recurly.com'
    port     : 443
    path     : '/v2/' + operation
    method   : method
    auth     : API_KEY + ':'
    headers  :
      accept           : 'application/xml'
      'content-length' : data.length

  if data.length > 0
    options.headers['content-type'] = 'application/xml; charset=utf-8'

  req = https.request options, (res) ->
    res.setEncoding 'utf8'
    body = ''
    res.on 'data', (chunk) ->
      body += chunk
    res.on 'end', () ->
      # Find next page's URL if necessary
      if res.headers.link
        nextUrl = parseRecurlyLink(res).next
      # Parse XML and build JS object, or list of errors.
      if body.length > 0
        xml2js.parseString body, (e, r) ->
          if e
            errors = [Error('xml', 'Invalid XML', '')]
            callback errors, {}
          else if r.error
            errors = [Error(r.error.symbol[0], r.error.description[0]['_'], '')]
            callback errors, {}
          else if r.errors
            # Build list of errors
            # See https://docs.recurly.com/api/basics/validation-errors for XML format
            errors = []
            for err in r.errors.error
              errors.push Error(err['$']['symbol'], err['_'], err['$']['field'])
            callback errors, {}
          else
            callback no, r, nextUrl
      else
        callback no, {}

  req.on 'error', (e) ->
    errors = [Error('connection', 'Unable to connect to payment backend.' , '')]
    callback errors, {}

  if data.length > 0
    req.write data
  req.end()

# POST request
recurlyPost = (operation, data, callback) ->
  recurlyRequest 'POST', operation, data, (e, r) ->
    callback e, r

# GET request.
# If there are multiple pages, this method requests all pages.
# Returns list of XML/JS objects, not a single XML/JS object!
recurlyGet = (operation, callback) ->
  if operation.indexOf('per_page') == -1
    if operation.indexOf('?') == -1
      operation += '?per_page=50'
    else
      operation += '&per_page=50'

  # Collect all GET responses before calling CB
  allData = []
  collect = (e, r, n)->
    if e
      callback e, []
    else
      allData.push r
      unless n
        callback no, allData
        allData = []
      else
        recurlyRequest 'GET', n, '', collect

  recurlyRequest 'GET', operation, '', collect

# PUT request
recurlyPut = (operation, data, callback) ->
  recurlyRequest 'PUT', operation, data, (e, r) ->
    callback e, r

# DELETE request
recurlyDel = (operation, callback) ->
  recurlyRequest 'DELETE', operation, '', (e, r) ->
    callback e, r

# Returns list of plans on recurly
getPlans = (callback) ->
  recurlyGet 'plans', (err, res) ->
    if err
      callback err, {}
    else
      plans = []
      for page in res
        # See https://docs.recurly.com/api/plans for XML format
        if page.plans?.plan
          for p in page.plans.plan
            plans.push
              title      : p.name[0]
              code       : p.plan_code[0]
              desc       : if typeof p.description[0] is "string" then p.description[0] else ""
              feeMonthly : parseInt p.unit_amount_in_cents[0]['USD'][0]['_']
              feeInitial : parseInt p.setup_fee_in_cents[0]['USD'][0]['_']
      callback no, plans

getPlanInfo = (data, callback)->
  recurlyGet "plans/#{data.code}", (err, res) ->
    if err
      callback err, {}
    else
      plan = []
      for page in res
        # See https://docs.recurly.com/api/plans for XML format
        if page.plan
          plan =
            code       : page.plan.plan_code[0]
            title      : page.plan.name[0]
            desc       : if typeof page.plan.description[0] is "string" then page.plan.description[0] else ""
            feeMonthly : parseInt page.plan.unit_amount_in_cents[0]['USD'][0]['_']
            feeInitial : parseInt page.plan.setup_fee_in_cents[0]['USD'][0]['_']
          callback no, plan
        else
          callback yes, {}
        break

# # This method is for user sellables.
# getUserPlans = (username, callback) ->
#   recurlyGet 'plans', (err, res) ->
#     if err
#       callback err, {}
#     else
#       plans = []
#       for page in res
#         # See https://docs.recurly.com/api/plans for XML format
#         if page.plans?.plan
#           for p in page.plans.plan
#             if p.plan_code[0].indexOf("user_#{username}_") > -1
#               plans.push
#                 title  : p.name[0]
#                 name   : p.plan_code[0]
#                 desc   : if p.description[0].$ then "" else p.description[0]
#                 amount : parseInt p.unit_amount_in_cents[0]['USD'][0]['_']
#                 initial: parseInt p.setup_fee_in_cents[0]['USD'][0]['_']
#       callback no, plans

addPlan = (data, callback) ->
  # See https://docs.recurly.com/api/plans for XML format
  data.feeInitial ?= 0 
  xml = """<plan>
             <plan_code>#{data.code}</plan_code>
             <name>#{data.title}</name>
             <setup_fee_in_cents>
               <USD>#{parseInt(data.feeInitial)}</USD>
             </setup_fee_in_cents>
             <unit_amount_in_cents>
               <USD>#{parseInt(data.feeMonthly)}</USD>
             </unit_amount_in_cents>
             <plan_interval_length>1</plan_interval_length>
             <plan_interval_unit>months</plan_interval_unit>
           </plan>"""

  recurlyPost 'plans', xml, (err, res) ->
    if err
      callback err, {}
    else
      plan =
        code       : res.plan.plan_code[0]
        title      : res.plan.name[0]
        desc       : if typeof res.plan.description[0] is "string" then res.plan.description[0] else ""
        feeMonthly : parseInt res.plan.unit_amount_in_cents[0]['USD'][0]['_']
        feeInitial : parseInt res.plan.setup_fee_in_cents[0]['USD'][0]['_']
      callback no, plan

deletePlan = (data, callback) ->
  recurlyDel "plans/#{data.code}", (err, res) ->
    if err
      callback err, {}
    else
      callback no, res

getUserSubscriptions = (accountCode, callback) ->
  recurlyGet "accounts/#{accountCode}/subscriptions", (err, res) ->
    if err
      callback err, []
    else
      plans = []
      for page in res
        # See https://docs.recurly.com/api/subscriptions for XML format
        if page.subscriptions?.subscription
          for s in page.subscriptions.subscription
            if s.state[0] not in ['expired', 'failed']
              plans.push
                plan     : s.plan[0].plan_code[0]
                quantity : s.quantity[0]['_']
                uuid     : s.uuid[0]
                status   : s.state[0]
                datetime : s.activated_at[0]['_']
                expires  : s.expires_at[0]['_']
                renew    : s.current_period_ends_at[0]['_']
      callback no, plans

getSubscriptionInfo = (accountCode, data, callback) ->
  recurlyGet "subscriptions/#{data.uuid}", (err, res) ->
    if err
      callback err, []
    else
      for page in res
        # See https://docs.recurly.com/api/subscriptions for XML format
        if page.subscription
          plan =
            plan     : page.subscription.plan[0].plan_code[0]
            quantity : page.subscription.quantity[0]['_']
            uuid     : page.subscription.uuid[0]
            status   : page.subscription.state[0]
            datetime : page.subscription.activated_at[0]['_']
            expires  : page.subscription.expires_at[0]['_']
            renew    : page.subscription.current_period_ends_at[0]['_']
          callback no, plan
        else
          callback yes, {}

addUserSubscription = (accountCode, data, callback) ->
  data.quantity ?= 1

  # See https://docs.recurly.com/api/subscriptions for XML format
  xml = """<?xml version="1.0" encoding="UTF-8"?>
            <subscription>
              <plan_code>#{data.plan}</plan_code>
              <currency>USD</currency>
              <quantity>#{data.quantity}</quantity>
              <account>
                <account_code>#{accountCode}</account_code>
              </account>
            </subscription>"""

  recurlyPost 'subscriptions', xml, (err, res) ->
    if err
      callback err, {}
    else
      subs =
        uuid       : res.subscription.uuid[0]
        plan       : res.subscription.plan[0].plan_code[0]
        quantity   : res.subscription.quantity[0]['_']
        status     : res.subscription.state[0]
        currency   : res.subscription.currency[0]
        datetime   : res.subscription.activated_at[0]['_']
        expires    : res.subscription.expires_at[0]['_']
        renew      : res.subscription.current_period_ends_at[0]['_']
      callback no, subs

updateUserSubscription = (accountCode, data, callback) ->
  data.quantity ?= 1

  # See https://docs.recurly.com/api/subscriptions for XML format
  xml = """<?xml version="1.0" encoding="UTF-8"?>
           <subscription>
             <timeframe>now</timeframe>
             <plan_code>#{data.plan}</plan_code>
             <quantity>#{data.quantity}</quantity>
           </subscription>"""

  recurlyPut "subscriptions/#{data.uuid}", xml, (err, res) ->
    if err
      callback err, {}
    else
      subs =
        uuid       : res.subscription.uuid[0]
        plan       : res.subscription.plan[0].plan_code[0]
        quantity   : res.subscription.quantity[0]['_']
        status     : res.subscription.state[0]
        currency   : res.subscription.currency[0]
        datetime   : res.subscription.activated_at[0]['_']
        expires    : res.subscription.expires_at[0]['_']
        renew      : res.subscription.current_period_ends_at[0]['_']
      callback no, subs

cancelUserSubscription = (accountCode, data, callback) ->
  recurlyPut "subscriptions/#{data.uuid}/cancel", "", (err, res) ->
    if err
      callback err, {}
    else
      subs =
        uuid       : res.subscription.uuid[0]
        plan       : res.subscription.plan[0].plan_code[0]
        quantity   : res.subscription.quantity[0]['_']
        status     : res.subscription.state[0]
        currency   : res.subscription.currency[0]
        datetime   : res.subscription.activated_at[0]['_']
        expires    : res.subscription.expires_at[0]['_']
        renew      : res.subscription.current_period_ends_at[0]['_']
      callback no, subs

reactivateUserSubscription = (accountCode, data, callback) ->
  recurlyPut "subscriptions/#{data.uuid}/reactivate", "", (err, res) ->
    if err
      callback err, {}
    else
      subs =
        uuid       : res.subscription.uuid[0]
        plan       : res.subscription.plan[0].plan_code[0]
        quantity   : res.subscription.quantity[0]['_']
        status     : res.subscription.state[0]
        currency   : res.subscription.currency[0]
        datetime   : res.subscription.activated_at[0]['_']
        expires    : res.subscription.expires_at[0]['_']
        renew      : res.subscription.current_period_ends_at[0]['_']
      callback no, subs
      
getUserTransactions = (accountCode, callback) ->
  recurlyGet "accounts/#{accountCode}/transactions", (err, res) ->
    if err
      callback err, []
    else
      transactions = []
      for page in res
        # See https://docs.recurly.com/api/transactions for XML format
        if page.transactions?.transaction
          for t in page.transactions.transaction
            account = t.details[0].account[0]
            billing = t.details[0].account[0].billing_info[0]
            transactions.push
              uuid      : t.uuid[0]
              status    : t.status[0]
              amount    : parseInt t.amount_in_cents[0]['_']
              tax       : parseInt t.tax_in_cents[0]['_']
              currency  : t.currency[0]
              datetime  : t.created_at[0]['_']
              refundable: t.refundable[0]['_']
              owner     : "#{billing.first_name[0]} #{billing.last_name[0]}"
              card      : "#{billing.card_type[0]} - #{billing.first_six.toString().substring(0,4)}-...-#{billing.last_four}"
              source    : t.source[0]
      callback no, transactions

getUserInvoices = (accountCode, callback)->
   recurlyGet "accounts/#{accountCode}/invoices", (err, res) ->
    if err
      callback err, []
    else
      invoices = []
      for page in res
        # See https://docs.recurly.com/api/invoices for XML format
        if page.invoices?.invoice
          for t in page.invoices.invoice
            invoices.push
              uuid      : t.uuid[0]
              amount    : t.total_in_cents[0]['_']
              currency  : t.currency[0]
              state     : t.state[0]
      callback no, invoices

getUserAdjustments = (accountCode, type, callback)->
   recurlyGet "accounts/#{accountCode}/adjustments?state=" + type, (err, res) ->
    if err
      callback err, []
    else
      adjustments = []
      for page in res
        # See https://docs.recurly.com/api/adjustments for XML format
        if page.adjustments?.adjustment
          for t in page.adjustments.adjustment
            adjustments.push
              uuid        : t.uuid[0]
              amount      : t.total_in_cents[0]['_']
              currency    : t.currency[0]
              description : if t.description[0].$ then "" else t.description[0]
              origin      : t.origin[0]
      callback no, adjustments

getUserCharged = (accountCode, callback)->
  getUserAdjustments accountCode, 'invoiced', callback

getUserPending = (accountCode, callback)->
  getUserAdjustments accountCode, 'pending', callback

chargeUserPending = (accountCode, callback)->
  recurlyPost "accounts/#{accountCode}/invoices", '', (err, res) ->
    if err
      callback err, {}
    else
      if res.invoice
        invoice = 
          uuid     : res.invoice.uuid[0]
          amount   : res.invoice.total_in_cents[0]['_']
          currency : res.invoice.currency[0]
        callback no, invoice
      else
        callback no, {}

addUserCharge = (accountCode, data, callback)->
  # See https://docs.recurly.com/api/adjustments for XML format
  xml = """<adjustment>
             <description>#{data.desc}</description>
             <unit_amount_in_cents>#{data.amount}</unit_amount_in_cents>
             <currency>USD</currency>
             <quantity>1</quantity>
             <accounting_code>#{data.accountingCode}</accounting_code>
           </adjustment>"""

  recurlyPost "accounts/#{accountCode}/adjustments", xml, (err, res)->
    if err
      callback err, {}
    else
      charge =
        uuid     : res.adjustment.uuid[0]
        amount   : res.adjustment.total_in_cents[0]['_']
        currency : res.adjustment.currency[0]
      callback no, charge

getBilling = (accountCode, callback) ->
  recurlyGet "accounts/#{accountCode}/billing_info", (err, res) ->
    if err
      callback err, {}
    else
      for page in res
        # See https://docs.recurly.com/api/billing-info for XML format
        if page.billing_info
          info = page.billing_info
          billing =
            cardFirstName     : info.first_name[0]
            cardLastName      : info.last_name[0]
            cardNumber        : info.first_six.toString().substring(0,4) + '-...-' + info.last_four
            cardMonth         : info.month[0]['_']
            cardYear          : info.year[0]['_']
            cardCV            : 'XXX'
            cardType          : info.card_type[0]
            address1          : info.address1[0]
            address2          : info.address2[0]
            city              : info.city[0]
            state             : info.state[0]
            zip               : info.zip[0]
            country           : info.country[0]
        else
          billing = {}
      callback no, billing

getAccount = (accountCode, callback) ->
  recurlyGet "accounts/#{accountCode}", (err, res) ->
    if err
      callback err, {}
    else
      for page in res
        # See https://docs.recurly.com/api/account for XML format
        if page.account
          account =
            firstName : page.account.first_name[0]
            lastName  : page.account.last_name[0]
            email     : page.account.email[0]
            username  : page.account.username[0]
            url       : 'https://koding.recurly.com/account/' + page.account.hosted_login_token[0]
          getBilling accountCode, (e, r) ->
            unless e
              for k, v of r
                account[k] = v
            callback no, account
        else
          callback no, {}

deleteAccount = (accountCode, callback) ->
  recurlyDel "accounts/#{accountCode}/billing_info", (err, res) ->
    if err
      callback err, {}
    else
      callback no, res

setAccount = (accountCode, data, callback) ->
  # Remove credit card number if it's not updated
  if data.cardNumber.indexOf('...') > -1 or data.cardCV == 'XXX'
    delete data.cardNumber
    delete data.cardCV

  # See https://docs.recurly.com/api/account for XML format
  xml = """<?xml version="1.0" encoding="UTF-8"?>
           <account>
             <account_code>#{accountCode}</account_code>
{'fraction_non_alphanumeric': 0.12982293146791998, 'fraction_numerical': 0.010629577002951142, 'mean_word_length': 2.036087605774017, 'pattern_counts': {'":': 0, '<': 115, '<?xml version=': 6, '>': 248, 'https://': 34, 'lorem ipsum': 0, 'www.': 0, 'xml': 36}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7653755', 'n_tokens_mistral': 11183, 'n_tokens_neox': 11196, 'n_words': 2527}