2014-11-21 23:34:30 +00:00
|
|
|
require 'recurly'
|
|
|
|
|
module JamRuby
|
2015-04-10 20:19:08 +00:00
|
|
|
class RecurlyClient
|
2015-04-03 20:34:12 +00:00
|
|
|
def initialize()
|
|
|
|
|
@log = Logging.logger[self]
|
2014-11-21 23:34:30 +00:00
|
|
|
end
|
|
|
|
|
|
2015-03-20 13:48:00 +00:00
|
|
|
def create_account(current_user, billing_info)
|
2014-11-21 23:34:30 +00:00
|
|
|
options = account_hash(current_user, billing_info)
|
2014-11-25 20:35:05 +00:00
|
|
|
account = nil
|
2014-11-21 23:34:30 +00:00
|
|
|
begin
|
2015-02-19 07:06:50 +00:00
|
|
|
#puts "Recurly.api_key: #{Recurly.api_key}"
|
2014-11-21 23:34:30 +00:00
|
|
|
account = Recurly::Account.create(options)
|
2020-11-21 22:14:37 +00:00
|
|
|
if account.errors.any?
|
|
|
|
|
puts "Errors encountered while creating account: #{account.errors}"
|
|
|
|
|
raise RecurlyClientError.new(account.errors) if account.errors.any?
|
|
|
|
|
end
|
2015-04-10 20:19:08 +00:00
|
|
|
rescue Recurly::Error, NoMethodError => x
|
2014-11-21 23:34:30 +00:00
|
|
|
raise RecurlyClientError, x.to_s
|
|
|
|
|
else
|
2014-11-25 20:35:05 +00:00
|
|
|
if account
|
2015-04-10 20:19:08 +00:00
|
|
|
current_user.update_attribute(:recurly_code, account.account_code)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
account
|
2014-11-21 23:34:30 +00:00
|
|
|
end
|
|
|
|
|
|
2015-04-01 01:25:23 +00:00
|
|
|
def has_account?(current_user)
|
2015-04-10 20:19:08 +00:00
|
|
|
account = get_account(current_user)
|
2015-04-01 01:25:23 +00:00
|
|
|
!!account
|
|
|
|
|
end
|
|
|
|
|
|
2014-11-21 23:34:30 +00:00
|
|
|
def delete_account(current_user)
|
2015-04-10 20:19:08 +00:00
|
|
|
account = get_account(current_user)
|
2020-11-21 22:14:37 +00:00
|
|
|
if account
|
2014-11-21 23:34:30 +00:00
|
|
|
begin
|
2015-04-10 20:19:08 +00:00
|
|
|
account.destroy
|
2014-11-21 23:34:30 +00:00
|
|
|
rescue Recurly::Error, NoMethodError => x
|
|
|
|
|
raise RecurlyClientError, x.to_s
|
|
|
|
|
end
|
2015-04-10 20:19:08 +00:00
|
|
|
else
|
2014-11-21 23:34:30 +00:00
|
|
|
raise RecurlyClientError, "Could not find account to delete."
|
|
|
|
|
end
|
|
|
|
|
account
|
|
|
|
|
end
|
2015-04-10 20:19:08 +00:00
|
|
|
|
2014-11-21 23:34:30 +00:00
|
|
|
def get_account(current_user)
|
2020-11-21 22:14:37 +00:00
|
|
|
account = current_user && current_user.recurly_code ? Recurly::Account.find(current_user.recurly_code) : nil
|
|
|
|
|
|
|
|
|
|
# check again, assuming account_code is the user ID (can happen in error scenarios where we create the account
|
|
|
|
|
# on recurly, but couldn't save the account_code to the user.recurly_code field)
|
|
|
|
|
|
|
|
|
|
if !account
|
|
|
|
|
account = Recurly::Account.find(current_user.id)
|
|
|
|
|
# repair user local account info
|
|
|
|
|
if !account.nil?
|
|
|
|
|
current_user.update_attribute(:recurly_code, account.account_code)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
account
|
|
|
|
|
|
2015-03-20 21:15:13 +00:00
|
|
|
rescue Recurly::Error => x
|
|
|
|
|
raise RecurlyClientError, x.to_s
|
2014-11-21 23:34:30 +00:00
|
|
|
end
|
|
|
|
|
|
2020-11-21 22:14:37 +00:00
|
|
|
|
2014-11-21 23:34:30 +00:00
|
|
|
def update_account(current_user, billing_info=nil)
|
|
|
|
|
account = get_account(current_user)
|
|
|
|
|
if(account.present?)
|
2015-04-10 20:19:08 +00:00
|
|
|
options = account_hash(current_user, billing_info)
|
2014-11-21 23:34:30 +00:00
|
|
|
begin
|
2015-04-10 20:19:08 +00:00
|
|
|
account.update_attributes(options)
|
2014-11-21 23:34:30 +00:00
|
|
|
rescue Recurly::Error, NoMethodError => x
|
|
|
|
|
raise RecurlyClientError, x.to_s
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
account
|
|
|
|
|
end
|
|
|
|
|
|
2015-04-12 18:45:26 +00:00
|
|
|
def payment_history(current_user, options ={})
|
|
|
|
|
|
|
|
|
|
limit = params[:limit]
|
|
|
|
|
limit ||= 20
|
|
|
|
|
limit = limit.to_i
|
|
|
|
|
|
|
|
|
|
cursor = options[:cursor]
|
|
|
|
|
|
2015-03-24 22:13:09 +00:00
|
|
|
payments = []
|
|
|
|
|
account = get_account(current_user)
|
|
|
|
|
if(account.present?)
|
|
|
|
|
begin
|
2015-04-12 18:45:26 +00:00
|
|
|
|
|
|
|
|
account.transaction.paginate(per_page:limit, cursor:cursor).each do |transaction|
|
2015-04-03 20:34:12 +00:00
|
|
|
# XXX this isn't correct because we create 0 dollar transactions too (for free stuff)
|
|
|
|
|
#if transaction.amount_in_cents > 0 # Account creation adds a transaction record
|
2015-03-24 22:13:09 +00:00
|
|
|
payments << {
|
|
|
|
|
:created_at => transaction.created_at,
|
|
|
|
|
:amount_in_cents => transaction.amount_in_cents,
|
|
|
|
|
:status => transaction.status,
|
|
|
|
|
:payment_method => transaction.payment_method,
|
2015-04-12 18:45:26 +00:00
|
|
|
:reference => transaction.reference,
|
|
|
|
|
:plan_code => transaction.plan_code
|
2015-03-24 22:13:09 +00:00
|
|
|
}
|
2015-04-03 20:34:12 +00:00
|
|
|
#end
|
2015-03-24 22:13:09 +00:00
|
|
|
end
|
|
|
|
|
rescue Recurly::Error, NoMethodError => x
|
|
|
|
|
raise RecurlyClientError, x.to_s
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
payments
|
|
|
|
|
end
|
|
|
|
|
|
2020-11-21 22:14:37 +00:00
|
|
|
def update_billing_info(current_user, billing_info=nil, account = nil)
|
|
|
|
|
account = get_account(current_user) if account.nil?
|
|
|
|
|
if account.present?
|
2014-11-21 23:34:30 +00:00
|
|
|
begin
|
2015-03-20 13:48:00 +00:00
|
|
|
account.billing_info = billing_info
|
|
|
|
|
account.billing_info.save
|
2014-11-21 23:34:30 +00:00
|
|
|
rescue Recurly::Error, NoMethodError => x
|
|
|
|
|
raise RecurlyClientError, x.to_s
|
|
|
|
|
end
|
|
|
|
|
|
2015-04-10 20:19:08 +00:00
|
|
|
raise RecurlyClientError.new(account.errors) if account.errors.any?
|
2014-11-21 23:34:30 +00:00
|
|
|
else
|
|
|
|
|
raise RecurlyClientError, "Could not find account to update billing info."
|
|
|
|
|
end
|
|
|
|
|
account
|
|
|
|
|
end
|
|
|
|
|
|
2020-11-21 22:14:37 +00:00
|
|
|
# token was created in the web ui. we can tell recurly to update the billing info on the account with just the token
|
|
|
|
|
def update_billing_info_from_token(current_user, account, recurly_token)
|
|
|
|
|
account.billing_info = {
|
|
|
|
|
token_id: recurly_token
|
|
|
|
|
}
|
|
|
|
|
account.billing_info.save!
|
|
|
|
|
end
|
|
|
|
|
|
2015-02-19 22:40:19 +00:00
|
|
|
def refund_user_subscription(current_user, jam_track)
|
|
|
|
|
jam_track_right=JamRuby::JamTrackRight.where("user_id=? AND jam_track_id=?", current_user.id, jam_track.id).first
|
|
|
|
|
if jam_track_right
|
|
|
|
|
refund_subscription(jam_track_right)
|
|
|
|
|
else
|
|
|
|
|
raise RecurlyClientError, "The user #{current_user} does not have a subscription to #{jam_track}"
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def refund_subscription(jam_track_right)
|
|
|
|
|
account = get_account(jam_track_right.user)
|
|
|
|
|
if (account.present?)
|
|
|
|
|
terminated = false
|
|
|
|
|
begin
|
|
|
|
|
jam_track = jam_track_right.jam_track
|
|
|
|
|
account.subscriptions.find_each do |subscription|
|
2015-03-04 22:31:03 +00:00
|
|
|
#puts "subscription.plan.plan_code: #{subscription.plan.plan_code} / #{jam_track.plan_code} / #{subscription.plan.plan_code == jam_track.plan_code}"
|
2015-02-19 22:40:19 +00:00
|
|
|
if(subscription.plan.plan_code == jam_track.plan_code)
|
|
|
|
|
subscription.terminate(:full)
|
2015-04-10 20:19:08 +00:00
|
|
|
raise RecurlyClientError.new(subscription.errors) if subscription.errors.any?
|
2015-02-19 22:40:19 +00:00
|
|
|
terminated = true
|
|
|
|
|
end
|
2015-04-10 20:19:08 +00:00
|
|
|
end
|
2015-02-19 22:40:19 +00:00
|
|
|
|
|
|
|
|
if terminated
|
2015-04-10 20:19:08 +00:00
|
|
|
jam_track_right.destroy()
|
2015-02-19 22:40:19 +00:00
|
|
|
else
|
|
|
|
|
raise RecurlyClientError, "Subscription '#{jam_track.plan_code}' not found for this user; could not issue refund."
|
|
|
|
|
end
|
2015-04-10 20:19:08 +00:00
|
|
|
|
2015-02-19 22:40:19 +00:00
|
|
|
rescue Recurly::Error, NoMethodError => x
|
|
|
|
|
raise RecurlyClientError, x.to_s
|
|
|
|
|
end
|
2015-04-10 20:19:08 +00:00
|
|
|
|
2015-02-19 22:40:19 +00:00
|
|
|
else
|
|
|
|
|
raise RecurlyClientError, "Could not find account to refund order."
|
|
|
|
|
end
|
|
|
|
|
account
|
|
|
|
|
end
|
|
|
|
|
|
2015-03-09 14:44:12 +00:00
|
|
|
def find_jam_track_plan(jam_track)
|
|
|
|
|
plan = nil
|
|
|
|
|
begin
|
|
|
|
|
plan = Recurly::Plan.find(jam_track.plan_code)
|
|
|
|
|
rescue Recurly::Resource::NotFound
|
|
|
|
|
end
|
|
|
|
|
plan
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def create_jam_track_plan(jam_track)
|
|
|
|
|
plan = Recurly::Plan.create(accounting_code: "",
|
|
|
|
|
bypass_hosted_confirmation: false,
|
|
|
|
|
cancel_url: nil,
|
|
|
|
|
description: jam_track.description,
|
|
|
|
|
display_donation_amounts: false,
|
|
|
|
|
display_phone_number: false,
|
|
|
|
|
display_quantity: false,
|
|
|
|
|
name: "JamTrack: #{jam_track.name}",
|
|
|
|
|
payment_page_css: nil,
|
|
|
|
|
payment_page_tos_link: nil,
|
|
|
|
|
plan_code: jam_track.plan_code,
|
|
|
|
|
plan_interval_length: 1,
|
|
|
|
|
plan_interval_unit: "months",
|
|
|
|
|
setup_fee_in_cents: Recurly::Money.new(:USD => 0), # <Recurly::Money USD: 0_00>
|
|
|
|
|
success_url: "",
|
|
|
|
|
tax_exempt: false,
|
|
|
|
|
total_billing_cycles: 1,
|
|
|
|
|
trial_interval_length: 0,
|
|
|
|
|
trial_interval_unit: "days",
|
|
|
|
|
unit_amount_in_cents: Recurly::Money.new(:USD => 1_99),
|
|
|
|
|
unit_name: "unit"
|
|
|
|
|
)
|
|
|
|
|
raise RecurlyClientError.new(plan.errors) if plan.errors.any?
|
|
|
|
|
end
|
|
|
|
|
|
2020-10-09 22:22:20 +00:00
|
|
|
# https://dev.recurly.com/docs/create-subscription
|
|
|
|
|
def create_subscription(user, plan_code, account)
|
2020-11-21 22:14:37 +00:00
|
|
|
puts "Creating subscription for #{user.email} with plan_code #{plan_code}"
|
2020-10-09 22:22:20 +00:00
|
|
|
subscription = Recurly::Subscription.create(
|
|
|
|
|
:plan_code => plan_code,
|
|
|
|
|
:currency => 'USD',
|
|
|
|
|
:customer_notes => 'Thank you for your business!',
|
|
|
|
|
:account => {
|
|
|
|
|
:account_code => account.account_code
|
|
|
|
|
},
|
|
|
|
|
:auto_renew => true
|
|
|
|
|
)
|
|
|
|
|
subscription
|
|
|
|
|
end
|
|
|
|
|
|
2020-11-21 22:14:37 +00:00
|
|
|
def find_subscription(user, account = nil)
|
|
|
|
|
subscription = nil
|
|
|
|
|
|
2020-10-09 22:22:20 +00:00
|
|
|
if user.recurly_subscription_id.nil?
|
2020-11-21 22:14:37 +00:00
|
|
|
if account.nil?
|
|
|
|
|
account = get_account(user)
|
|
|
|
|
end
|
|
|
|
|
if account
|
|
|
|
|
account.subscriptions.find_each do |subscription|
|
|
|
|
|
puts "Subscription: #{subscription.inspect}"
|
|
|
|
|
end
|
|
|
|
|
subscription = account.subscriptions.first
|
|
|
|
|
else
|
|
|
|
|
puts "can't find subscription for account #{account}"
|
|
|
|
|
end
|
2020-10-09 22:22:20 +00:00
|
|
|
else
|
2020-11-21 22:14:37 +00:00
|
|
|
subscription = Recurly::Subscription.find(user.recurly_subscription_id)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if user.recurly_subscription_id.nil?
|
|
|
|
|
puts "Repairing subscription ID on account"
|
|
|
|
|
user.update_attribute(:recurly_subscription_id, subscription.id)
|
|
|
|
|
user.recurly_subscription_id = subscription.id
|
2020-10-09 22:22:20 +00:00
|
|
|
end
|
2020-11-21 22:14:37 +00:00
|
|
|
|
|
|
|
|
subscription
|
2020-10-09 22:22:20 +00:00
|
|
|
end
|
|
|
|
|
|
2020-11-21 22:14:37 +00:00
|
|
|
def change_subscription_plan(current_user, plan_code)
|
|
|
|
|
subscription = find_subscription(current_user)
|
|
|
|
|
|
|
|
|
|
if subscription.nil?
|
|
|
|
|
puts "no subscription found for user #{current_user.email}"
|
|
|
|
|
return false
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
puts "subscription.plan #{subscription.plan}"
|
|
|
|
|
if subscription.plan.plan_code == plan_code
|
|
|
|
|
puts "plan code was the same as requested: #{plan_code}"
|
|
|
|
|
return false
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
result = subscription.update_attributes(
|
|
|
|
|
:plan_code => plan_code,
|
|
|
|
|
:timeframe => 'bill_date'
|
|
|
|
|
)
|
|
|
|
|
puts "change subscription plan #{result}"
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
2020-10-09 22:22:20 +00:00
|
|
|
def sync_subscription(user)
|
|
|
|
|
subscription = find_subscription(user)
|
|
|
|
|
|
|
|
|
|
if subscription.nil?
|
|
|
|
|
if user.subscription_plan_code
|
|
|
|
|
user.subscription_plan_code = nil
|
|
|
|
|
user.recurly_subscription_state = nil
|
|
|
|
|
user.save(validate:false)
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
user.recurly_subscription_state = subscription.state
|
|
|
|
|
if user.subscription_plan_code != subscription.plan.plan_code
|
|
|
|
|
user.subscription_plan_code = subscription.plan.plan_code
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2014-11-21 23:34:30 +00:00
|
|
|
|
2020-11-21 22:14:37 +00:00
|
|
|
def find_or_create_account(current_user, billing_info, recurly_token = nil)
|
2014-11-21 23:34:30 +00:00
|
|
|
account = get_account(current_user)
|
2015-04-10 20:19:08 +00:00
|
|
|
|
2020-11-21 22:14:37 +00:00
|
|
|
if !account
|
2014-11-21 23:34:30 +00:00
|
|
|
account = create_account(current_user, billing_info)
|
2020-11-21 22:14:37 +00:00
|
|
|
elsif !billing_info.nil?
|
|
|
|
|
update_billing_info(current_user, billing_info, account)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if !recurly_token.nil?
|
|
|
|
|
update_billing_info_from_token(current_user, account, recurly_token)
|
2015-04-10 20:19:08 +00:00
|
|
|
end
|
|
|
|
|
account
|
2014-11-21 23:34:30 +00:00
|
|
|
end
|
2015-04-10 20:19:08 +00:00
|
|
|
|
2014-11-21 23:34:30 +00:00
|
|
|
private
|
|
|
|
|
def account_hash(current_user, billing_info)
|
|
|
|
|
options = {
|
|
|
|
|
account_code: current_user.id,
|
|
|
|
|
email: current_user.email,
|
|
|
|
|
first_name: current_user.first_name,
|
|
|
|
|
last_name: current_user.last_name,
|
|
|
|
|
address: {
|
|
|
|
|
city: current_user.city,
|
|
|
|
|
state: current_user.state,
|
|
|
|
|
country: current_user.country
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-04-10 20:19:08 +00:00
|
|
|
|
2014-11-21 23:34:30 +00:00
|
|
|
options[:billing_info] = billing_info if billing_info
|
2015-05-07 14:02:58 +00:00
|
|
|
|
2014-11-21 23:34:30 +00:00
|
|
|
options
|
|
|
|
|
end
|
|
|
|
|
end # class
|
|
|
|
|
|
|
|
|
|
class RecurlyClientError < Exception
|
2014-11-25 20:35:05 +00:00
|
|
|
attr_accessor :errors
|
|
|
|
|
def initialize(data)
|
2015-04-10 20:19:08 +00:00
|
|
|
if data.respond_to?('has_key?')
|
|
|
|
|
self.errors = data
|
2014-11-25 20:35:05 +00:00
|
|
|
else
|
|
|
|
|
self.errors = {:message=>data.to_s}
|
2015-04-10 20:19:08 +00:00
|
|
|
end
|
2014-11-25 20:35:05 +00:00
|
|
|
end # initialize
|
2014-12-02 00:45:41 +00:00
|
|
|
|
|
|
|
|
def to_s
|
|
|
|
|
s=super
|
|
|
|
|
s << ", errors: #{errors.inspect}" if self.errors.any?
|
|
|
|
|
s
|
|
|
|
|
end
|
|
|
|
|
|
2014-11-25 20:35:05 +00:00
|
|
|
end # RecurlyClientError
|
2014-11-21 23:34:30 +00:00
|
|
|
end # module
|
|
|
|
|
|