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
2020-11-30 00:24:28 +00:00
def update_desired_subscription ( current_user , plan_code )
subscription = nil
account = nil
current_user . desired_plan_code = plan_code
current_user . desired_plan_code_set_at = DateTime . now
current_user . save ( validate : false )
puts " updating desired subscription for #{ current_user . email } to #{ plan_code } "
account = get_account ( current_user )
if account
if plan_code . nil? || plan_code == ''
begin
# user wants a free subscription. If they have a subscription, let's cancel it.
subscription , account = find_subscription ( current_user , account )
if subscription
puts " Canceling user's #{ current_user . email } subscription "
subscription . cancel
2021-01-17 01:37:34 +00:00
# upon cancelation, take the user's current monthly payment plan
subscription = Recurly :: Subscription . find ( subscription . uuid )
current_user . subscription_plan_code = get_highest_plan ( subscription )
current_user . subscription_plan_code_set_at = DateTime . now
current_user . save ( validate : false )
2020-11-30 00:24:28 +00:00
# do not delete the recurly_subscription_id ; we'll use that to try and reactivate later if they user re-activates their account
else
# if no subscription and past trial, you goin down -- because there must have never been payment??
if current_user . subscription_trial_ended?
current_user . subscription_plan_code = nil
current_user . subscription_plan_code_set_at = DateTime . now
current_user . save ( validate : false )
end
end
# do not set the subscription _plan_code either; because the user has paid through the month; they still
# get their old plan
#current_user.subscription_plan_code = nil
#current_user.save(validate: false)
rescue = > e
puts " Could not cancel subscription for user #{ current_user . email } . #{ e } "
return false , subscription , account
end
else
# user wants to pay. let's get it goin
return handle_create_subscription ( current_user , plan_code , account )
end
end
return true , subscription , account
end
2014-11-21 23:34:30 +00:00
def get_account ( current_user )
2021-01-04 13:40:49 +00:00
begin
account = current_user && current_user . recurly_code ? Recurly :: Account . find ( current_user . recurly_code ) : nil
rescue Recurly :: Error = > x
puts " Swallow find acct for user #{ current_user . email } error initial #{ x } "
end
2020-11-21 22:14:37 +00:00
# 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)
2020-11-30 00:24:28 +00:00
puts " get_account for #{ current_user . email } found #{ account } "
2020-11-21 22:14:37 +00:00
if ! account
2020-11-30 00:24:28 +00:00
begin
account = Recurly :: Account . find ( current_user . id )
rescue Recurly :: Error = > x
2021-01-04 13:40:49 +00:00
puts " Swallow find acct for user #{ current_user . email } error #{ x } "
2020-11-30 00:24:28 +00:00
end
2020-11-21 22:14:37 +00:00
# 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
2020-11-30 00:24:28 +00:00
def list_invoices ( account )
invoices = [ ]
count = 0
account . invoices . find_each do | invoice |
count = count + 1
invoices << invoice
if count == 50
break
end
end
invoices
end
def payment_history ( current_user , params = { } )
2015-04-12 18:45:26 +00:00
limit = params [ :limit ]
limit || = 20
limit = limit . to_i
2020-11-30 00:24:28 +00:00
cursor = params [ :cursor ]
2015-04-12 18:45:26 +00:00
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
2020-11-30 00:24:28 +00:00
account . transactions . 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 ,
2020-11-30 00:24:28 +00:00
:tax_in_cents = > transaction . tax_in_cents ,
2015-03-24 22:13:09 +00:00
:status = > transaction . status ,
2020-11-30 00:24:28 +00:00
:action = > transaction . action ,
2015-03-24 22:13:09 +00:00
:payment_method = > transaction . payment_method ,
2015-04-12 18:45:26 +00:00
:reference = > transaction . reference ,
2020-11-30 00:24:28 +00:00
:currency = > transaction . currency
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
2020-11-30 00:24:28 +00:00
puts " Recurly error #{ current_user . email } #{ x } "
2015-03-24 22:13:09 +00:00
raise RecurlyClientError , x . to_s
end
end
payments
end
2020-11-30 00:24:28 +00:00
def invoice_history ( current_user , params = { } )
limit = params [ :limit ]
limit || = 20
limit = limit . to_i
cursor = params [ :cursor ]
payments = [ ]
account = get_account ( current_user )
if ( account . present? )
begin
account . invoices . paginate ( per_page : limit , cursor : cursor ) . each do | invoice |
# 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
payments << {
:created_at = > invoice . created_at ,
:subtotal_in_cents = > invoice . subtotal_in_cents ,
:tax_in_cents = > invoice . tax_in_cents ,
:total_in_cents = > invoice . total_in_cents ,
:state = > invoice . state ,
:description = > invoice . line_items . map ( & :description ) . join ( " , " ) ,
:currency = > invoice . currency
}
#end
end
rescue Recurly :: Error , NoMethodError = > x
puts " Recurly error #{ current_user . email } #{ x } "
raise RecurlyClientError , x . to_s
end
end
return payments , account
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
2021-01-17 01:37:34 +00:00
def get_pending_plan_code ( subscription )
if subscription && subscription . pending_subscription
return subscription . pending_subscription . plan . plan_code
else
return nil
end
end
def get_highest_plan ( subscription )
SubscriptionDefinitions . higher_plan ( subscription . plan . plan_code , get_pending_plan_code ( subscription ) )
end
2020-11-30 00:24:28 +00:00
def handle_create_subscription ( current_user , plan_code , account )
begin
subscription = create_subscription ( current_user , plan_code , account , current_user . subscription_trial_ended? ? nil : current_user . subscription_trial_ends_at )
current_user . recurly_subscription_id = subscription . uuid
2021-02-15 04:32:27 +00:00
current_user . first_subscribed_at = Time . now if current_user . first_subscribed_at . nil?
2021-04-21 21:27:55 +00:00
current_user . first_subscribed_plan_code = plan_code if current_user . first_subscribed_plan_code . nil?
2020-11-30 00:24:28 +00:00
if current_user . subscription_trial_ended?
2021-01-17 01:37:34 +00:00
current_user . subscription_plan_code = get_highest_plan ( subscription )
2020-11-30 00:24:28 +00:00
current_user . subscription_plan_code_set_at = DateTime . now
else
# we could force a platinum plan since the user has put forward payment already, even in trial
puts " user #{ current_user . email } is in trial "
if plan_code == SubscriptionDefinitions :: JAM_PLATINUM || plan_code == SubscriptionDefinitions :: JAM_PLATINUM_YEARLY
puts " user #{ current_user . email } is in trial and buying platinum ; upgrade them already "
current_user . subscription_plan_code = plan_code
current_user . subscription_plan_code_set_at = DateTime . now
else
current_user . subscription_plan_code = SubscriptionDefinitions :: JAM_GOLD
current_user . subscription_plan_code_set_at = DateTime . now
end
end
2021-02-21 21:07:31 +00:00
current_user . reset_playtime
2020-11-30 00:24:28 +00:00
current_user . save ( validate : false )
rescue = > e
puts " Could not create subscription for user #{ current_user . email } . #{ e } "
return false , subscription , account
end
return true , subscription , account
end
2020-10-09 22:22:20 +00:00
# https://dev.recurly.com/docs/create-subscription
2020-11-30 00:24:28 +00:00
def create_subscription ( user , plan_code , account , starts_at = nil )
old_subscription_id = user . recurly_subscription_id
if old_subscription_id
# first, let's try to reactivate it
old_subscription = Recurly :: Subscription . find ( old_subscription_id )
begin
old_subscription . reactivate
puts " reactivated plan! Let's check if it needs changing "
2021-01-17 01:37:34 +00:00
#if plan_code != old_subscription.plan.plan_code
2020-11-30 00:24:28 +00:00
result = old_subscription . update_attributes (
:plan_code = > plan_code ,
:timeframe = > starts_at . nil? ? 'bill_date' : 'now'
)
2021-01-17 01:37:34 +00:00
# end
# fetch it again. because it's got staleness after update_attributes operation
return Recurly :: Subscription . find ( old_subscription_id )
2020-11-30 00:24:28 +00:00
rescue = > e
puts " Unable to reactivate/update old plan #{ e } "
user . update_attribute ( :recurly_subscription_id , nil )
end
end
if account . billing_info
puts " Creating subscription for #{ user . email } with plan_code #{ plan_code } "
subscription = Recurly :: Subscription . create (
:plan_code = > plan_code ,
:currency = > 'USD' ,
:customer_notes = > 'Thank you for your business!' ,
:account = > {
:account_code = > account . account_code
} ,
:starts_at = > starts_at ,
:auto_renew = > true
)
subscription
else
puts " User has no billing info; not trying to create a subscription #{ user . email } "
end
2020-10-09 22:22:20 +00:00
subscription
end
2020-11-30 00:24:28 +00:00
def find_subscription ( user , fed_account = nil )
2020-11-21 22:14:37 +00:00
subscription = nil
2020-11-30 00:24:28 +00:00
account = nil
if fed_account . nil?
account = get_account ( user )
else
account = fed_account
end
2020-11-21 22:14:37 +00:00
2021-01-06 03:45:35 +00:00
# first try to find the current subscription. If it's gone, delete our state. If expired, delete our state.
if user . recurly_subscription_id
begin
subscription = Recurly :: Subscription . find ( user . recurly_subscription_id )
rescue Recurly :: Resource :: NotFound
puts " subscription is gone. delete it! "
user . update_attribute ( :recurly_subscription_id , nil )
user . recurly_subscription_id = nil
subscription = nil
end
puts " Subscription state: #{ subscription . state } "
if subscription . state == 'expired'
puts " subscription is expired. stop tracking it! "
user . update_attribute ( :recurly_subscription_id , nil )
user . recurly_subscription_id = nil
subscription = nil
end
end
2020-10-09 22:22:20 +00:00
if user . recurly_subscription_id . nil?
2020-11-21 22:14:37 +00:00
if account
2020-11-30 00:24:28 +00:00
active_subscription = nil
2020-11-21 22:14:37 +00:00
account . subscriptions . find_each do | subscription |
2020-11-30 00:24:28 +00:00
puts " Subscription: #{ subscription . inspect } #{ subscription . state } "
2021-01-06 03:45:35 +00:00
if subscription . state == " active " || subscription . state == " future "
2020-11-30 00:24:28 +00:00
active_subscription = subscription
break
end
2020-11-21 22:14:37 +00:00
end
2020-11-30 00:24:28 +00:00
subscription = active_subscription
2020-11-21 22:14:37 +00:00
else
puts " can't find subscription for account #{ account } "
end
end
2020-11-30 00:24:28 +00:00
if subscription && user . recurly_subscription_id . nil?
2020-11-21 22:14:37 +00:00
puts " Repairing subscription ID on account "
2021-01-06 03:45:35 +00:00
user . update_attribute ( :recurly_subscription_id , subscription . uuid )
2021-04-21 21:27:55 +00:00
user . update_attribute ( :first_subscribed_plan_code , subscription . plan . plan_code )
2021-01-06 03:45:35 +00:00
user . recurly_subscription_id = subscription . uuid
2021-02-15 04:32:27 +00:00
user . first_subscribed_at = Time . now if user . first_subscribed_at . nil?
2020-10-09 22:22:20 +00:00
end
2020-11-21 22:14:37 +00:00
2020-11-30 00:24:28 +00:00
return [ subscription , account ]
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 )
2020-11-30 00:24:28 +00:00
subscription , account = find_subscription ( current_user )
2020-11-21 22:14:37 +00:00
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 )
2020-11-30 00:24:28 +00:00
begin
# edge case: admin controlled
if user . admin_override_plan_code
2020-12-05 18:16:38 +00:00
# check if it's expired first...
if Time . now > user . admin_override_ends_at . to_time
puts " admin control expired. clear override and set Free plan "
user . admin_override_plan_code = nil
# logic below will catch this
#user.subscription_plan_code = nil
user . admin_override_ends_at = nil
user . subscription_sync_code = 'undo_admin_control'
user . subscription_sync_msg = " admin control expired. clear override and set Free plan "
user . subscription_last_checked_at = Time . now
user . save ( validate : false )
# don't return; let this fall through to next states
else
puts " admin controlled plan #{ user . email } "
user . subscription_plan_code = user . admin_override_plan_code
user . subscription_plan_code_set_at = Time . now
user . subscription_last_checked_at = Time . now
user . subscription_sync_code = 'admin_control'
user . subscription_sync_msg = " admin override - plan_code set to #{ user . admin_override_plan_code } "
user . save ( validate : false )
return
end
2020-11-30 00:24:28 +00:00
end
# edge case: user is in a licensed school
if user . has_active_license?
puts " user has school license #{ user . email } "
user . subscription_plan_code = SubscriptionDefinitions :: JAM_PLATINUM
user . subscription_plan_code_set_at = DateTime . now
user . subscription_last_checked_at = DateTime . now
user . subscription_sync_code = 'school_license'
user . subscription_sync_msg = " has school license - plan_code set to #{ SubscriptionDefinitions :: JAM_PLATINUM } "
user . save ( validate : false )
return
end
# if user is in trial still, not much book-keeping
if ! user . subscription_trial_ended?
puts " user has a trial still #{ user . email } "
# there is actually nothing to do, because we don't start billing for any plan until trial is over.
user . subscription_last_checked_at = DateTime . now
user . subscription_sync_code = 'in_trial'
user . subscription_sync_msg = " trial still active - plan_code not altered "
user . save ( validate : false )
return
end
# if there is no recurly action here, then they must be coming off of a trial and we have to mark them down
if user . recurly_code . nil? && ! user . subscription_plan_code . nil?
puts " new user #{ user . email } has no payment info and is ending their trial "
# TODO: send email
2020-10-09 22:22:20 +00:00
user . subscription_plan_code = nil
2020-11-30 00:24:28 +00:00
user . subscription_plan_code_set_at = DateTime . now
user . subscription_last_checked_at = DateTime . now
user . subscription_sync_code = 'trial_ended'
user . subscription_sync_msg = " trial ended and no subscription set - plan_code set to Free "
user . save ( validate : false )
return
end
account = get_account ( user )
if account . nil?
2020-12-05 18:16:38 +00:00
puts " Account is nil? #{ user . email } . Strange. Could happen in some weird admin messing around scenarios "
2020-11-30 00:24:28 +00:00
user . subscription_last_checked_at = DateTime . now
user . save ( validate : false )
user . subscription_sync_code = 'no_recurly_account'
user . subscription_sync_msg = " user has no recurly account - plan_code not altered "
2020-12-05 18:16:38 +00:00
user . save ( validate : false )
2020-11-30 00:24:28 +00:00
return
end
user . is_past_due = account . has_past_due_invoice
subscription , account = find_subscription ( user , account )
if subscription
user . recurly_subscription_state = subscription . state
else
2020-10-09 22:22:20 +00:00
user . recurly_subscription_state = nil
end
2020-11-30 00:24:28 +00:00
if subscription . nil? || subscription . state == 'expired'
puts " user has expired or no plan "
user . subscription_plan_code = nil
user . subscription_plan_code_set_at = DateTime . now
user . subscription_sync_code = 'no_subscription_or_expired'
user . subscription_sync_msg = " user has no or expired subscription - plan_code set to Free "
else
if user . is_past_due
if ! user . subscription_plan_code . nil?
puts " user #{ user . email } has a past due plan. We gotta bring them down "
user . subscription_plan_code = nil
user . subscription_plan_code_set_at = DateTime . now
user . subscription_sync_code = 'is_past_due_changed'
user . subscription_sync_msg = " payment has gone past due - plan_code set to Free "
else
puts " user is past due and #{ user . email } had no changes "
user . subscription_sync_code = 'is_past_due_unchanged'
user . subscription_sync_msg = " payment has gone past due, plan_code not altered because already set to free "
end
else
2021-01-08 04:39:09 +00:00
if user . subscription_plan_code != user . desired_plan_code
2020-11-30 00:24:28 +00:00
puts " they are back! get them back into their desired plan #{ user . email } "
2021-01-11 18:15:15 +00:00
if ! SubscriptionDefinitions . is_downgrade ( user . desired_plan_code , user . subscription_plan_code )
2021-02-21 21:07:31 +00:00
user . reset_playtime
2021-01-11 18:15:15 +00:00
user . subscription_plan_code = user . desired_plan_code
user . subscription_plan_code_set_at = DateTime . now
user . subscription_sync_code = 'good_standing_repaired'
user . subscription_sync_msg = " user is in good standing but desired != effective; plan_code set to #{ user . desired_plan_code } "
else
#user.subscription_plan_code = user.desired_plan_code
#user.subscription_plan_code_set_at = DateTime.now
user . subscription_sync_code = 'good_standing_ignored'
user . subscription_sync_msg = " user is in good standing but the desired plan is less than subscription plan; plan_code not touched "
end
2020-11-30 00:24:28 +00:00
else
puts " good standing user #{ user . email } had no changes "
user . subscription_sync_code = 'good_standing_unchanged'
user . subscription_sync_msg = " user is in good standing but already set correctly; plan_code not altered "
end
end
2020-10-09 22:22:20 +00:00
end
2020-11-30 00:24:28 +00:00
user . subscription_last_checked_at = DateTime . now
user . save ( validate : false )
rescue = > e
puts " Unexpected error in sync_subscription for user #{ user . email } "
puts e . message
user . subscription_last_checked_at = DateTime . now
user . subscription_sync_code = 'failed_sync'
user . subscription_sync_msg = e . message
user . save ( validate : false )
2020-10-09 22:22:20 +00:00
end
2020-11-30 00:24:28 +00:00
2020-10-09 22:22:20 +00:00
end
2014-11-21 23:34:30 +00:00
2021-02-15 04:32:27 +00:00
def sync_transactions ( options = { } )
ActiveRecord :: Base . transaction do
2021-03-14 21:23:43 +00:00
options . merge! ( { sort : :updated_at , state : :successful , per_page : 200 } )
latest_seen = nil
2021-02-15 04:32:27 +00:00
Recurly :: Transaction . find_each ( options ) do | transaction |
if AffiliateDistribution . find_by_external_id ( transaction . uuid )
2021-03-16 20:23:56 +00:00
# this is now a normal path, because we will pick up the last transaction we saw, since we use
# the last transaction time to feed `latest_seen`.
#begin
# Bugsnag.notify("ActiveRecord::RecordNotUnique: duplicate affiliate_distribution for Recurly transaction uuid #{transaction.uuid} was prevented from been added.")
#rescue => exception
# Rails.logger.error(exception) unless Rails.env.test?
#end
2021-02-15 04:32:27 +00:00
next
end
2021-03-16 13:35:47 +00:00
# advance the time last seen in the transactions.
if latest_seen . nil? || latest_seen < transaction . created_at . to_time
latest_seen = transaction . created_at . to_time
end
2021-02-15 04:32:27 +00:00
# these next lines try to ascertain that the transaction we've hit describes a true subscription
# jamtrack transactions are handled entirely separately, so this should avoid those, and perhaps other 'odd'
# transactions in Recurly
2021-03-16 18:34:50 +00:00
next if transaction . action != 'purchase' || transaction . status != 'success' || transaction . source != 'subscription'
2021-02-15 04:32:27 +00:00
next if transaction . subscriptions . length == 0
subscription = transaction . subscriptions . first
next if subscription . plan == nil || subscription . plan . plan_code == nil
account = transaction . details [ " account " ]
user = User . find ( account . account_code )
affiliate_partner = user . affiliate_referral
if ! affiliate_partner . nil? && affiliate_partner . created_within_affiliate_window ( user , transaction . created_at . to_time )
affiliate_distribution = AffiliateDistribution . new
affiliate_distribution . product_type = " Subscription "
affiliate_distribution . affiliate_referral = affiliate_partner
2021-03-16 15:09:55 +00:00
fee_in_cents = ( transaction . amount_in_cents - transaction . tax_in_cents ) * affiliate_partner . rate
2021-02-15 04:32:27 +00:00
affiliate_distribution . affiliate_referral_fee_in_cents = fee_in_cents
affiliate_distribution . created_at = transaction . created_at . to_time
affiliate_distribution . product_code = subscription . plan . plan_code
affiliate_distribution . external_id = transaction . uuid #external_id is a unique column. should raises error if duplicates
affiliate_distribution . save!
end
end
2021-03-14 21:23:43 +00:00
# only grab the latest time as seen in the data; that way, we should never skip really recent entries if
if ! latest_seen . nil?
GenericState . singleton . update_attribute ( :recurly_transactions_last_sync_at , latest_seen )
end
2021-02-15 04:32:27 +00:00
end
end
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
2021-02-15 04:32:27 +00:00
2014-11-21 23:34:30 +00:00
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