Class: Pay::LemonSqueezy::Subscription

Inherits:
Subscription
  • Object
show all
Extended by:
Sync
Defined in:
app/models/pay/lemon_squeezy/subscription.rb

Constant Summary collapse

STATUSES =

Lemon Squeezy statuses that Pay spells differently

{"on_trial" => "trialing", "cancelled" => "canceled"}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Sync

find_pay_customer, pay_processor, sync_with_retries

Class Method Details

.sync(subscription_id, object: nil, name: Pay.default_product_name) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'app/models/pay/lemon_squeezy/subscription.rb', line 9

def self.sync(subscription_id, object: nil, name: Pay.default_product_name)
  sync_with_retries do
    subscription = object || ::LemonSqueezy::Subscription.retrieve(id: subscription_id)
    return unless (pay_customer = find_pay_customer(subscription.customer_id))

    attributes = {
      current_period_end: subscription.renews_at,
      ends_at: (subscription.ends_at ? Time.parse(subscription.ends_at) : nil),
      pause_resumes_at: (subscription.pause&.resumes_at ? Time.parse(subscription.pause.resumes_at) : nil),
      status: STATUSES.fetch(subscription.status, subscription.status),
      processor_plan: subscription.first_subscription_item.price_id,
      quantity: subscription.first_subscription_item.quantity,
      created_at: (subscription.created_at ? Time.parse(subscription.created_at) : nil),
      updated_at: (subscription.updated_at ? Time.parse(subscription.updated_at) : nil)
    }

    case attributes[:status]
    when "canceled"
      # Remove payment methods since customer cannot be reused after cancelling
      pay_customer.payment_methods.destroy_all
    when "trialing"
      attributes[:trial_ends_at] = Time.parse(subscription.trial_ends_at)
    when "active", "past_due"
      attributes[:trial_ends_at] = nil
      attributes[:pause_resumes_at] = nil
      attributes[:ends_at] = nil
    end

    # Update or create the subscription
    if (pay_subscription = find_by(customer: pay_customer, processor_id: subscription.id))
      pay_subscription.with_lock { pay_subscription.update!(attributes) }
      pay_subscription
    else
      create!(attributes.merge(customer: pay_customer, name: name, processor_id: subscription.id))
    end
  end
end

Instance Method Details

#api_record(**options) ⇒ Object



47
48
49
50
51
# File 'app/models/pay/lemon_squeezy/subscription.rb', line 47

def api_record(**options)
  @api_record ||= ::LemonSqueezy::Subscription.retrieve(id: processor_id)
rescue ::LemonSqueezy::Error => e
  raise Pay::LemonSqueezy::Error, e
end

#cancel(**options) ⇒ Object



61
62
63
64
65
66
67
# File 'app/models/pay/lemon_squeezy/subscription.rb', line 61

def cancel(**options)
  return if canceled?
  response = ::LemonSqueezy::Subscription.cancel(id: processor_id)
  update(status: response.status, ends_at: response.ends_at)
rescue ::LemonSqueezy::Error => e
  raise Pay::LemonSqueezy::Error, e
end

#cancel_now!(**options) ⇒ Object



69
70
71
# File 'app/models/pay/lemon_squeezy/subscription.rb', line 69

def cancel_now!(**options)
  raise Pay::NotSupportedError, "Lemon Squeezy does not support cancelling immediately through the API"
end

#change_quantity(quantity, **options) ⇒ Object



73
74
75
76
77
78
79
# File 'app/models/pay/lemon_squeezy/subscription.rb', line 73

def change_quantity(quantity, **options)
  subscription_item = api_record.first_subscription_item
  ::LemonSqueezy::SubscriptionItem.update(id: subscription_item.id, quantity: quantity)
  update(quantity: quantity)
rescue ::LemonSqueezy::Error => e
  raise Pay::LemonSqueezy::Error, e
end

#pause(**options) ⇒ Object



85
86
87
88
89
90
# File 'app/models/pay/lemon_squeezy/subscription.rb', line 85

def pause(**options)
  response = ::LemonSqueezy::Subscription.pause(id: processor_id, **options)
  update!(status: :paused, pause_resumes_at: response.pause&.resumes_at)
rescue ::LemonSqueezy::Error => e
  raise Pay::LemonSqueezy::Error, e
end

#paused?Boolean

Returns:

  • (Boolean)


81
82
83
# File 'app/models/pay/lemon_squeezy/subscription.rb', line 81

def paused?
  status == "paused"
end

#portal_urlObject



53
54
55
# File 'app/models/pay/lemon_squeezy/subscription.rb', line 53

def portal_url
  api_record.urls.customer_portal
end

#resumable?Boolean

Returns:

  • (Boolean)


92
93
94
# File 'app/models/pay/lemon_squeezy/subscription.rb', line 92

def resumable?
  paused? || canceled?
end

#resumeObject



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'app/models/pay/lemon_squeezy/subscription.rb', line 96

def resume
  unless resumable?
    raise Error, "You can only resume paused or cancelled subscriptions"
  end

  if paused?
    ::LemonSqueezy::Subscription.unpause(id: processor_id)
  else
    ::LemonSqueezy::Subscription.uncancel(id: processor_id)
  end

  update(ends_at: nil, status: :active, pause_resumes_at: nil)
rescue ::LemonSqueezy::Error => e
  raise Pay::LemonSqueezy::Error, e
end

#swap(plan, **options) ⇒ Object

Lemon Squeezy requires both the Product ID and Variant ID. The Variant ID will be saved as the processor_plan

Raises:



114
115
116
117
118
119
120
121
# File 'app/models/pay/lemon_squeezy/subscription.rb', line 114

def swap(plan, **options)
  raise Error, "A plan_id is required to swap a subscription" unless plan
  raise Error, "A variant_id is required to swap a subscription" unless options[:variant_id]

  ::LemonSqueezy::Subscription.change_plan id: processor_id, plan_id: plan, variant_id: options[:variant_id]

  update(processor_plan: options[:variant_id], ends_at: nil, status: :active)
end

#update_urlObject



57
58
59
# File 'app/models/pay/lemon_squeezy/subscription.rb', line 57

def update_url
  api_record.urls.update_payment_method
end