pay 7.3.0 → 8.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/README.md +2 -0
 - data/app/controllers/pay/webhooks/lemon_squeezy_controller.rb +45 -0
 - data/app/jobs/pay/customer_sync_job.rb +1 -1
 - data/app/models/concerns/pay/routing.rb +13 -0
 - data/{lib → app/models}/pay/braintree/charge.rb +5 -12
 - data/{lib/pay/braintree/billable.rb → app/models/pay/braintree/customer.rb} +31 -71
 - data/{lib → app/models}/pay/braintree/payment_method.rb +1 -9
 - data/{lib → app/models}/pay/braintree/subscription.rb +15 -53
 - data/app/models/pay/charge.rb +8 -27
 - data/app/models/pay/customer.rb +2 -15
 - data/app/models/pay/fake_processor/charge.rb +13 -0
 - data/{lib/pay/fake_processor/billable.rb → app/models/pay/fake_processor/customer.rb} +20 -37
 - data/{lib → app/models}/pay/fake_processor/merchant.rb +2 -9
 - data/app/models/pay/fake_processor/payment_method.rb +11 -0
 - data/app/models/pay/fake_processor/subscription.rb +60 -0
 - data/app/models/pay/lemon_squeezy/charge.rb +86 -0
 - data/app/models/pay/lemon_squeezy/customer.rb +78 -0
 - data/app/models/pay/lemon_squeezy/payment_method.rb +27 -0
 - data/app/models/pay/lemon_squeezy/subscription.rb +129 -0
 - data/app/models/pay/merchant.rb +0 -11
 - data/{lib → app/models}/pay/paddle_billing/charge.rb +2 -8
 - data/{lib/pay/paddle_billing/billable.rb → app/models/pay/paddle_billing/customer.rb} +18 -35
 - data/{lib → app/models}/pay/paddle_billing/payment_method.rb +2 -12
 - data/{lib → app/models}/pay/paddle_billing/subscription.rb +9 -33
 - data/{lib → app/models}/pay/paddle_classic/charge.rb +13 -18
 - data/{lib/pay/paddle_classic/billable.rb → app/models/pay/paddle_classic/customer.rb} +9 -31
 - data/{lib → app/models}/pay/paddle_classic/payment_method.rb +1 -11
 - data/{lib → app/models}/pay/paddle_classic/subscription.rb +11 -36
 - data/app/models/pay/payment_method.rb +0 -5
 - data/{lib → app/models}/pay/stripe/charge.rb +6 -22
 - data/{lib/pay/stripe/billable.rb → app/models/pay/stripe/customer.rb} +73 -108
 - data/{lib → app/models}/pay/stripe/merchant.rb +2 -11
 - data/{lib → app/models}/pay/stripe/payment_method.rb +2 -10
 - data/{lib → app/models}/pay/stripe/subscription.rb +37 -71
 - data/app/models/pay/subscription.rb +7 -37
 - data/app/models/pay/webhook.rb +3 -1
 - data/config/routes.rb +1 -0
 - data/db/migrate/2_add_pay_sti_columns.rb +24 -0
 - data/lib/pay/attributes.rb +11 -3
 - data/lib/pay/braintree.rb +25 -6
 - data/lib/pay/engine.rb +2 -0
 - data/lib/pay/fake_processor.rb +2 -6
 - data/lib/pay/lemon_squeezy/webhooks/order.rb +11 -0
 - data/lib/pay/lemon_squeezy/webhooks/subscription.rb +3 -3
 - data/lib/pay/lemon_squeezy/webhooks/subscription_payment.rb +11 -0
 - data/lib/pay/lemon_squeezy.rb +56 -104
 - data/lib/pay/paddle_billing.rb +15 -6
 - data/lib/pay/paddle_classic.rb +11 -9
 - data/lib/pay/receipts.rb +6 -6
 - data/lib/pay/stripe/webhooks/customer_updated.rb +1 -1
 - data/lib/pay/stripe.rb +16 -7
 - data/lib/pay/version.rb +1 -1
 - data/lib/pay.rb +12 -1
 - metadata +34 -38
 - data/app/views/pay/stripe/_checkout_button.html.erb +0 -21
 - data/lib/pay/braintree/authorization_error.rb +0 -9
 - data/lib/pay/braintree/error.rb +0 -23
 - data/lib/pay/fake_processor/charge.rb +0 -21
 - data/lib/pay/fake_processor/error.rb +0 -6
 - data/lib/pay/fake_processor/payment_method.rb +0 -21
 - data/lib/pay/fake_processor/subscription.rb +0 -90
 - data/lib/pay/lemon_squeezy/billable.rb +0 -90
 - data/lib/pay/lemon_squeezy/charge.rb +0 -68
 - data/lib/pay/lemon_squeezy/error.rb +0 -7
 - data/lib/pay/lemon_squeezy/payment_method.rb +0 -40
 - data/lib/pay/lemon_squeezy/subscription.rb +0 -185
 - data/lib/pay/lemon_squeezy/webhooks/transaction_completed.rb +0 -11
 - data/lib/pay/paddle_billing/error.rb +0 -7
 - data/lib/pay/paddle_classic/error.rb +0 -7
 - data/lib/pay/stripe/error.rb +0 -7
 
| 
         @@ -1,32 +1,23 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module Pay
         
     | 
| 
       2 
2 
     | 
    
         
             
              module FakeProcessor
         
     | 
| 
       3 
     | 
    
         
            -
                class  
     | 
| 
       4 
     | 
    
         
            -
                   
     | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
       6 
     | 
    
         
            -
                   
     | 
| 
       7 
     | 
    
         
            -
             
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
       10 
     | 
    
         
            -
                    : 
     | 
| 
       11 
     | 
    
         
            -
                     
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
                  def initialize(pay_customer)
         
     | 
| 
       14 
     | 
    
         
            -
                    @pay_customer = pay_customer
         
     | 
| 
       15 
     | 
    
         
            -
                  end
         
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
       17 
     | 
    
         
            -
                  def customer
         
     | 
| 
       18 
     | 
    
         
            -
                    pay_customer.update!(processor_id: NanoId.generate) unless processor_id?
         
     | 
| 
       19 
     | 
    
         
            -
                    pay_customer
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Customer < Pay::Customer
         
     | 
| 
      
 4 
     | 
    
         
            +
                  has_many :charges, dependent: :destroy, class_name: "Pay::FakeProcessor::Charge"
         
     | 
| 
      
 5 
     | 
    
         
            +
                  has_many :subscriptions, dependent: :destroy, class_name: "Pay::FakeProcessor::Subscription"
         
     | 
| 
      
 6 
     | 
    
         
            +
                  has_many :payment_methods, dependent: :destroy, class_name: "Pay::FakeProcessor::PaymentMethod"
         
     | 
| 
      
 7 
     | 
    
         
            +
                  has_one :default_payment_method, -> { where(default: true) }, class_name: "Pay::FakeProcessor::PaymentMethod"
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def api_record
         
     | 
| 
      
 10 
     | 
    
         
            +
                    update!(processor_id: NanoId.generate) unless processor_id?
         
     | 
| 
      
 11 
     | 
    
         
            +
                    self
         
     | 
| 
       20 
12 
     | 
    
         
             
                  end
         
     | 
| 
       21 
13 
     | 
    
         | 
| 
       22 
     | 
    
         
            -
                  def  
     | 
| 
       23 
     | 
    
         
            -
                     
     | 
| 
       24 
     | 
    
         
            -
                    customer
         
     | 
| 
      
 14 
     | 
    
         
            +
                  def update_api_record(**attributes)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    self
         
     | 
| 
       25 
16 
     | 
    
         
             
                  end
         
     | 
| 
       26 
17 
     | 
    
         | 
| 
       27 
18 
     | 
    
         
             
                  def charge(amount, options = {})
         
     | 
| 
       28 
19 
     | 
    
         
             
                    # Make to generate a processor_id
         
     | 
| 
       29 
     | 
    
         
            -
                     
     | 
| 
      
 20 
     | 
    
         
            +
                    api_record
         
     | 
| 
       30 
21 
     | 
    
         | 
| 
       31 
22 
     | 
    
         
             
                    valid_attributes = options.slice(*Pay::Charge.attribute_names.map(&:to_sym))
         
     | 
| 
       32 
23 
     | 
    
         
             
                    attributes = {
         
     | 
| 
         @@ -40,12 +31,12 @@ module Pay 
     | 
|
| 
       40 
31 
     | 
    
         
             
                        exp_year: Date.today.year
         
     | 
| 
       41 
32 
     | 
    
         
             
                      }
         
     | 
| 
       42 
33 
     | 
    
         
             
                    }.deep_merge(valid_attributes)
         
     | 
| 
       43 
     | 
    
         
            -
                     
     | 
| 
      
 34 
     | 
    
         
            +
                    charges.create!(attributes)
         
     | 
| 
       44 
35 
     | 
    
         
             
                  end
         
     | 
| 
       45 
36 
     | 
    
         | 
| 
       46 
37 
     | 
    
         
             
                  def subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, **options)
         
     | 
| 
       47 
38 
     | 
    
         
             
                    # Make to generate a processor_id
         
     | 
| 
       48 
     | 
    
         
            -
                     
     | 
| 
      
 39 
     | 
    
         
            +
                    api_record
         
     | 
| 
       49 
40 
     | 
    
         
             
                    attributes = options.merge(
         
     | 
| 
       50 
41 
     | 
    
         
             
                      processor_id: NanoId.generate,
         
     | 
| 
       51 
42 
     | 
    
         
             
                      name: name,
         
     | 
| 
         @@ -60,17 +51,17 @@ module Pay 
     | 
|
| 
       60 
51 
     | 
    
         | 
| 
       61 
52 
     | 
    
         
             
                    attributes.delete(:promotion_code)
         
     | 
| 
       62 
53 
     | 
    
         | 
| 
       63 
     | 
    
         
            -
                     
     | 
| 
      
 54 
     | 
    
         
            +
                    subscriptions.create!(attributes)
         
     | 
| 
       64 
55 
     | 
    
         
             
                  end
         
     | 
| 
       65 
56 
     | 
    
         | 
| 
       66 
57 
     | 
    
         
             
                  def add_payment_method(payment_method_id, default: false)
         
     | 
| 
       67 
58 
     | 
    
         
             
                    # Make to generate a processor_id
         
     | 
| 
       68 
     | 
    
         
            -
                     
     | 
| 
      
 59 
     | 
    
         
            +
                    api_record
         
     | 
| 
       69 
60 
     | 
    
         | 
| 
       70 
     | 
    
         
            -
                    pay_payment_method =  
     | 
| 
      
 61 
     | 
    
         
            +
                    pay_payment_method = payment_methods.create!(
         
     | 
| 
       71 
62 
     | 
    
         
             
                      processor_id: NanoId.generate,
         
     | 
| 
       72 
63 
     | 
    
         
             
                      default: default,
         
     | 
| 
       73 
     | 
    
         
            -
                       
     | 
| 
      
 64 
     | 
    
         
            +
                      payment_method_type: :card,
         
     | 
| 
       74 
65 
     | 
    
         
             
                      data: {
         
     | 
| 
       75 
66 
     | 
    
         
             
                        brand: "Fake",
         
     | 
| 
       76 
67 
     | 
    
         
             
                        last4: 1234,
         
     | 
| 
         @@ -80,20 +71,12 @@ module Pay 
     | 
|
| 
       80 
71 
     | 
    
         
             
                    )
         
     | 
| 
       81 
72 
     | 
    
         | 
| 
       82 
73 
     | 
    
         
             
                    if default
         
     | 
| 
       83 
     | 
    
         
            -
                       
     | 
| 
       84 
     | 
    
         
            -
                       
     | 
| 
      
 74 
     | 
    
         
            +
                      payment_methods.where.not(id: pay_payment_method.id).update_all(default: false)
         
     | 
| 
      
 75 
     | 
    
         
            +
                      reload_default_payment_method
         
     | 
| 
       85 
76 
     | 
    
         
             
                    end
         
     | 
| 
       86 
77 
     | 
    
         | 
| 
       87 
78 
     | 
    
         
             
                    pay_payment_method
         
     | 
| 
       88 
79 
     | 
    
         
             
                  end
         
     | 
| 
       89 
     | 
    
         
            -
             
     | 
| 
       90 
     | 
    
         
            -
                  def processor_subscription(subscription_id, options = {})
         
     | 
| 
       91 
     | 
    
         
            -
                    pay_customer.subscriptions.find_by(processor_id: subscription_id)
         
     | 
| 
       92 
     | 
    
         
            -
                  end
         
     | 
| 
       93 
     | 
    
         
            -
             
     | 
| 
       94 
     | 
    
         
            -
                  def trial_end_date(subscription)
         
     | 
| 
       95 
     | 
    
         
            -
                    Date.today
         
     | 
| 
       96 
     | 
    
         
            -
                  end
         
     | 
| 
       97 
80 
     | 
    
         
             
                end
         
     | 
| 
       98 
81 
     | 
    
         
             
              end
         
     | 
| 
       99 
82 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,16 +1,9 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module Pay
         
     | 
| 
       2 
2 
     | 
    
         
             
              module FakeProcessor
         
     | 
| 
       3 
     | 
    
         
            -
                class Merchant
         
     | 
| 
       4 
     | 
    
         
            -
                  attr_reader :pay_merchant
         
     | 
| 
       5 
     | 
    
         
            -
                  delegate :processor_id, to: :pay_merchant
         
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
     | 
    
         
            -
                  def initialize(pay_merchant)
         
     | 
| 
       8 
     | 
    
         
            -
                    @pay_merchant = pay_merchant
         
     | 
| 
       9 
     | 
    
         
            -
                  end
         
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
      
 3 
     | 
    
         
            +
                class Merchant < Pay::Merchant
         
     | 
| 
       11 
4 
     | 
    
         
             
                  def create_account(**options)
         
     | 
| 
       12 
5 
     | 
    
         
             
                    fake_account = Struct.new(:id).new("fake_account_id")
         
     | 
| 
       13 
     | 
    
         
            -
                     
     | 
| 
      
 6 
     | 
    
         
            +
                    update(processor_id: fake_account.id)
         
     | 
| 
       14 
7 
     | 
    
         
             
                    fake_account
         
     | 
| 
       15 
8 
     | 
    
         
             
                  end
         
     | 
| 
       16 
9 
     | 
    
         | 
| 
         @@ -0,0 +1,60 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Pay
         
     | 
| 
      
 2 
     | 
    
         
            +
              module FakeProcessor
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Subscription < Pay::Subscription
         
     | 
| 
      
 4 
     | 
    
         
            +
                  def api_record(**options)
         
     | 
| 
      
 5 
     | 
    
         
            +
                    self
         
     | 
| 
      
 6 
     | 
    
         
            +
                  end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  # With trial, sets end to trial end (mimicing Stripe)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  # Without trial, sets can ends_at to end of month
         
     | 
| 
      
 10 
     | 
    
         
            +
                  def cancel(**options)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    return if canceled?
         
     | 
| 
      
 12 
     | 
    
         
            +
                    update(ends_at: (on_trial? ? trial_ends_at : Time.current.end_of_month))
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  def cancel_now!(**options)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    return if canceled?
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                    ends_at = Time.current
         
     | 
| 
      
 19 
     | 
    
         
            +
                    update(
         
     | 
| 
      
 20 
     | 
    
         
            +
                      status: :canceled,
         
     | 
| 
      
 21 
     | 
    
         
            +
                      trial_ends_at: (ends_at if trial_ends_at?),
         
     | 
| 
      
 22 
     | 
    
         
            +
                      ends_at: ends_at
         
     | 
| 
      
 23 
     | 
    
         
            +
                    )
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  def paused?
         
     | 
| 
      
 27 
     | 
    
         
            +
                    status == "paused"
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  def pause
         
     | 
| 
      
 31 
     | 
    
         
            +
                    update(status: :paused, trial_ends_at: Time.current)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  def resumable?
         
     | 
| 
      
 35 
     | 
    
         
            +
                    on_grace_period? || paused?
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  def resume
         
     | 
| 
      
 39 
     | 
    
         
            +
                    unless resumable?
         
     | 
| 
      
 40 
     | 
    
         
            +
                      raise StandardError, "You can only resume subscriptions within their grace period."
         
     | 
| 
      
 41 
     | 
    
         
            +
                    end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                    update(status: :active, trial_ends_at: nil, ends_at: nil)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                  def swap(plan, **options)
         
     | 
| 
      
 47 
     | 
    
         
            +
                    update(processor_plan: plan, ends_at: nil, status: :active)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                  def change_quantity(quantity, **options)
         
     | 
| 
      
 51 
     | 
    
         
            +
                    update(quantity: quantity)
         
     | 
| 
      
 52 
     | 
    
         
            +
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                  # Retries the latest invoice for a Past Due subscription
         
     | 
| 
      
 55 
     | 
    
         
            +
                  def retry_failed_payment
         
     | 
| 
      
 56 
     | 
    
         
            +
                    update(status: :active)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  end
         
     | 
| 
      
 58 
     | 
    
         
            +
                end
         
     | 
| 
      
 59 
     | 
    
         
            +
              end
         
     | 
| 
      
 60 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,86 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Pay
         
     | 
| 
      
 2 
     | 
    
         
            +
              module LemonSqueezy
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Charge < Pay::Charge
         
     | 
| 
      
 4 
     | 
    
         
            +
                  # LemonSqueezy uses Order for one-time payments and Order + Subscription + SubscriptionInvoice for subscriptions
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                  def self.sync_order(order_id, object: nil, try: 0, retries: 1)
         
     | 
| 
      
 7 
     | 
    
         
            +
                    object ||= ::LemonSqueezy::Order.retrieve(id: order_id)
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                    pay_customer = Pay::Customer.find_by(type: "Pay::LemonSqueezy::Customer", processor_id: object.customer_id)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    return unless pay_customer
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                    processor_id = "order:#{object.id}"
         
     | 
| 
      
 13 
     | 
    
         
            +
                    attributes = {
         
     | 
| 
      
 14 
     | 
    
         
            +
                      processor_id: processor_id,
         
     | 
| 
      
 15 
     | 
    
         
            +
                      currency: object.currency,
         
     | 
| 
      
 16 
     | 
    
         
            +
                      subtotal: object.subtotal,
         
     | 
| 
      
 17 
     | 
    
         
            +
                      tax: object.tax,
         
     | 
| 
      
 18 
     | 
    
         
            +
                      amount: object.total,
         
     | 
| 
      
 19 
     | 
    
         
            +
                      amount_refunded: object.refunded_amount,
         
     | 
| 
      
 20 
     | 
    
         
            +
                      created_at: (object.created_at ? Time.parse(object.created_at) : nil),
         
     | 
| 
      
 21 
     | 
    
         
            +
                      updated_at: (object.updated_at ? Time.parse(object.updated_at) : nil)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    }
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                    # Update or create the charge
         
     | 
| 
      
 25 
     | 
    
         
            +
                    if (pay_charge = pay_customer.charges.find_by(processor_id: processor_id))
         
     | 
| 
      
 26 
     | 
    
         
            +
                      pay_charge.with_lock do
         
     | 
| 
      
 27 
     | 
    
         
            +
                        pay_charge.update!(attributes)
         
     | 
| 
      
 28 
     | 
    
         
            +
                      end
         
     | 
| 
      
 29 
     | 
    
         
            +
                      pay_charge
         
     | 
| 
      
 30 
     | 
    
         
            +
                    else
         
     | 
| 
      
 31 
     | 
    
         
            +
                      pay_customer.charges.create!(attributes.merge(processor_id: processor_id))
         
     | 
| 
      
 32 
     | 
    
         
            +
                    end
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  def self.sync_subscription_invoice(subscription_invoice_id, object: nil)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    # Skip loading the latest subscription invoice details from the API if we already have it
         
     | 
| 
      
 37 
     | 
    
         
            +
                    object ||= ::LemonSqueezy::SubscriptionInvoice.retrieve(id: subscription_invoice_id)
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                    pay_customer = Pay::Customer.find_by(type: "Pay::LemonSqueezy::Customer", processor_id: object.customer_id)
         
     | 
| 
      
 40 
     | 
    
         
            +
                    return unless pay_customer
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                    processor_id = "subscription_invoice:#{object.id}"
         
     | 
| 
      
 43 
     | 
    
         
            +
                    subscription = Pay::LemonSqueezy::Subscription.find_by(processor_id: object.subscription_id)
         
     | 
| 
      
 44 
     | 
    
         
            +
                    attributes = {
         
     | 
| 
      
 45 
     | 
    
         
            +
                      processor_id: processor_id,
         
     | 
| 
      
 46 
     | 
    
         
            +
                      currency: object.currency,
         
     | 
| 
      
 47 
     | 
    
         
            +
                      amount: object.total,
         
     | 
| 
      
 48 
     | 
    
         
            +
                      amount_refunded: object.refunded_amount,
         
     | 
| 
      
 49 
     | 
    
         
            +
                      subtotal: object.subtotal,
         
     | 
| 
      
 50 
     | 
    
         
            +
                      tax: object.tax,
         
     | 
| 
      
 51 
     | 
    
         
            +
                      subscription: subscription,
         
     | 
| 
      
 52 
     | 
    
         
            +
                      payment_method_type: ("card" if object.card_brand.present?),
         
     | 
| 
      
 53 
     | 
    
         
            +
                      brand: object.card_brand,
         
     | 
| 
      
 54 
     | 
    
         
            +
                      last4: object.card_last_four,
         
     | 
| 
      
 55 
     | 
    
         
            +
                      created_at: (object.created_at ? Time.parse(object.created_at) : nil),
         
     | 
| 
      
 56 
     | 
    
         
            +
                      updated_at: (object.updated_at ? Time.parse(object.updated_at) : nil)
         
     | 
| 
      
 57 
     | 
    
         
            +
                    }
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                    # Update customer's payment method
         
     | 
| 
      
 60 
     | 
    
         
            +
                    Pay::LemonSqueezy::PaymentMethod.sync(pay_customer: pay_customer, attributes: object)
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                    # Update or create the charge
         
     | 
| 
      
 63 
     | 
    
         
            +
                    if (pay_charge = pay_customer.charges.find_by(processor_id: processor_id))
         
     | 
| 
      
 64 
     | 
    
         
            +
                      pay_charge.with_lock do
         
     | 
| 
      
 65 
     | 
    
         
            +
                        pay_charge.update!(attributes)
         
     | 
| 
      
 66 
     | 
    
         
            +
                      end
         
     | 
| 
      
 67 
     | 
    
         
            +
                      pay_charge
         
     | 
| 
      
 68 
     | 
    
         
            +
                    else
         
     | 
| 
      
 69 
     | 
    
         
            +
                      pay_customer.charges.create!(attributes.merge(processor_id: processor_id))
         
     | 
| 
      
 70 
     | 
    
         
            +
                    end
         
     | 
| 
      
 71 
     | 
    
         
            +
                  end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                  def api_record
         
     | 
| 
      
 74 
     | 
    
         
            +
                    ls_type, ls_id = processor_id.split(":", 2)
         
     | 
| 
      
 75 
     | 
    
         
            +
                    case ls_type
         
     | 
| 
      
 76 
     | 
    
         
            +
                    when "order"
         
     | 
| 
      
 77 
     | 
    
         
            +
                      ::LemonSqueezy::Order.retrieve(id: ls_id)
         
     | 
| 
      
 78 
     | 
    
         
            +
                    when "subscription_invoice"
         
     | 
| 
      
 79 
     | 
    
         
            +
                      ::LemonSqueezy::SubscriptionInvoice.retrieve(id: ls_id)
         
     | 
| 
      
 80 
     | 
    
         
            +
                    end
         
     | 
| 
      
 81 
     | 
    
         
            +
                  rescue ::LemonSqueezy::Error => e
         
     | 
| 
      
 82 
     | 
    
         
            +
                    raise Pay::LemonSqueezy::Error, e
         
     | 
| 
      
 83 
     | 
    
         
            +
                  end
         
     | 
| 
      
 84 
     | 
    
         
            +
                end
         
     | 
| 
      
 85 
     | 
    
         
            +
              end
         
     | 
| 
      
 86 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,78 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Pay
         
     | 
| 
      
 2 
     | 
    
         
            +
              module LemonSqueezy
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Customer < Pay::Customer
         
     | 
| 
      
 4 
     | 
    
         
            +
                  include Pay::Routing
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                  has_many :charges, dependent: :destroy, class_name: "Pay::LemonSqueezy::Charge"
         
     | 
| 
      
 7 
     | 
    
         
            +
                  has_many :subscriptions, dependent: :destroy, class_name: "Pay::LemonSqueezy::Subscription"
         
     | 
| 
      
 8 
     | 
    
         
            +
                  has_many :payment_methods, dependent: :destroy, class_name: "Pay::LemonSqueezy::PaymentMethod"
         
     | 
| 
      
 9 
     | 
    
         
            +
                  has_one :default_payment_method, -> { where(default: true) }, class_name: "Pay::LemonSqueezy::PaymentMethod"
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  def api_record_attributes
         
     | 
| 
      
 12 
     | 
    
         
            +
                    {email: email, name: customer_name}
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  # Retrieves a LemonSqueezy::Customer object
         
     | 
| 
      
 16 
     | 
    
         
            +
                  #
         
     | 
| 
      
 17 
     | 
    
         
            +
                  # Finds an existing LemonSqueezy::Customer if processor_id exists
         
     | 
| 
      
 18 
     | 
    
         
            +
                  # Creates a new LemonSqueezy::Customer using `email` and `customer_name` if empty processor_id
         
     | 
| 
      
 19 
     | 
    
         
            +
                  #
         
     | 
| 
      
 20 
     | 
    
         
            +
                  # Returns a LemonSqueezy::Customer object
         
     | 
| 
      
 21 
     | 
    
         
            +
                  def api_record
         
     | 
| 
      
 22 
     | 
    
         
            +
                    if processor_id?
         
     | 
| 
      
 23 
     | 
    
         
            +
                      ::LemonSqueezy::Customer.retrieve(id: processor_id)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    elsif (lsc = ::LemonSqueezy::Customer.list(store_id: Pay::LemonSqueezy.store_id, email: email).data.first)
         
     | 
| 
      
 25 
     | 
    
         
            +
                      update!(processor_id: lsc.id)
         
     | 
| 
      
 26 
     | 
    
         
            +
                      lsc
         
     | 
| 
      
 27 
     | 
    
         
            +
                    else
         
     | 
| 
      
 28 
     | 
    
         
            +
                      lsc = ::LemonSqueezy::Customer.create(store_id: Pay::LemonSqueezy.store_id, **api_record_attributes)
         
     | 
| 
      
 29 
     | 
    
         
            +
                      update!(processor_id: lsc.id)
         
     | 
| 
      
 30 
     | 
    
         
            +
                      lsc
         
     | 
| 
      
 31 
     | 
    
         
            +
                    end
         
     | 
| 
      
 32 
     | 
    
         
            +
                  rescue ::LemonSqueezy::Error => e
         
     | 
| 
      
 33 
     | 
    
         
            +
                    raise Pay::LemonSqueezy::Error, e
         
     | 
| 
      
 34 
     | 
    
         
            +
                  end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  # Syncs name and email to LemonSqueezy::Customer
         
     | 
| 
      
 37 
     | 
    
         
            +
                  # You can also pass in other attributes that will be merged into the default attributes
         
     | 
| 
      
 38 
     | 
    
         
            +
                  def update_api_record(**attributes)
         
     | 
| 
      
 39 
     | 
    
         
            +
                    api_record unless processor_id?
         
     | 
| 
      
 40 
     | 
    
         
            +
                    ::LemonSqueezy::Customer.update(id: processor_id, **api_record_attributes.merge(attributes))
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                  def charge(amount, options = {})
         
     | 
| 
      
 44 
     | 
    
         
            +
                    raise Pay::Error, "LemonSqueezy does not support one-off charges"
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                  def subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, **options)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                  def add_payment_method(token = nil, default: true)
         
     | 
| 
      
 51 
     | 
    
         
            +
                  end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                  # checkout(variant_id: "1234")
         
     | 
| 
      
 54 
     | 
    
         
            +
                  # checkout(variant_id: "1234", product_options: {redirect_url: redirect_url})
         
     | 
| 
      
 55 
     | 
    
         
            +
                  def checkout(**options)
         
     | 
| 
      
 56 
     | 
    
         
            +
                    api_record unless processor_id?
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                    options[:store_id] = Pay::LemonSqueezy.store_id
         
     | 
| 
      
 59 
     | 
    
         
            +
                    options[:product_options] ||= {}
         
     | 
| 
      
 60 
     | 
    
         
            +
                    options[:product_options][:redirect_url] = merge_order_id_param(options.dig(:product_options, :redirect_url) || root_url)
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                    ::LemonSqueezy::Checkout.create(**options)
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                  def portal_url
         
     | 
| 
      
 66 
     | 
    
         
            +
                    api_record.urls.customer_portal
         
     | 
| 
      
 67 
     | 
    
         
            +
                  end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                  private
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                  def merge_order_id_param(url)
         
     | 
| 
      
 72 
     | 
    
         
            +
                    uri = URI.parse(url)
         
     | 
| 
      
 73 
     | 
    
         
            +
                    uri.query = URI.encode_www_form(URI.decode_www_form(uri.query.to_s).to_h.merge("lemon_squeezy_order_id" => "[order_id]").to_a)
         
     | 
| 
      
 74 
     | 
    
         
            +
                    uri.to_s.gsub("%5Border_id%5D", "[order_id]")
         
     | 
| 
      
 75 
     | 
    
         
            +
                  end
         
     | 
| 
      
 76 
     | 
    
         
            +
                end
         
     | 
| 
      
 77 
     | 
    
         
            +
              end
         
     | 
| 
      
 78 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,27 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Pay
         
     | 
| 
      
 2 
     | 
    
         
            +
              module LemonSqueezy
         
     | 
| 
      
 3 
     | 
    
         
            +
                class PaymentMethod < Pay::PaymentMethod
         
     | 
| 
      
 4 
     | 
    
         
            +
                  def self.sync(pay_customer:, attributes:)
         
     | 
| 
      
 5 
     | 
    
         
            +
                    return unless pay_customer.subscription
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                    payment_method = pay_customer.default_payment_method || pay_customer.build_default_payment_method
         
     | 
| 
      
 8 
     | 
    
         
            +
                    payment_method.processor_id ||= NanoId.generate
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                    attrs = {
         
     | 
| 
      
 11 
     | 
    
         
            +
                      payment_method_type: "card",
         
     | 
| 
      
 12 
     | 
    
         
            +
                      brand: attributes.card_brand,
         
     | 
| 
      
 13 
     | 
    
         
            +
                      last4: attributes.card_last_four
         
     | 
| 
      
 14 
     | 
    
         
            +
                    }
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                    payment_method.update!(attrs)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    payment_method
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  def make_default!
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  def detach
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,129 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Pay
         
     | 
| 
      
 2 
     | 
    
         
            +
              module LemonSqueezy
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Subscription < Pay::Subscription
         
     | 
| 
      
 4 
     | 
    
         
            +
                  def self.sync(subscription_id, object: nil, name: Pay.default_product_name)
         
     | 
| 
      
 5 
     | 
    
         
            +
                    object ||= ::LemonSqueezy::Subscription.retrieve(id: subscription_id)
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                    pay_customer = Pay::Customer.find_by(processor: :lemon_squeezy, processor_id: object.customer_id)
         
     | 
| 
      
 8 
     | 
    
         
            +
                    return unless pay_customer
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                    attributes = {
         
     | 
| 
      
 11 
     | 
    
         
            +
                      current_period_end: object.renews_at,
         
     | 
| 
      
 12 
     | 
    
         
            +
                      ends_at: (object.ends_at ? Time.parse(object..ends_at) : nil),
         
     | 
| 
      
 13 
     | 
    
         
            +
                      pause_starts_at: (object.pause&.resumes_at ? Time.parse(object.pause.resumes_at) : nil),
         
     | 
| 
      
 14 
     | 
    
         
            +
                      status: object.status,
         
     | 
| 
      
 15 
     | 
    
         
            +
                      processor_plan: object.first_subscription_item.price_id,
         
     | 
| 
      
 16 
     | 
    
         
            +
                      quantity: object.first_subscription_item.quantity,
         
     | 
| 
      
 17 
     | 
    
         
            +
                      created_at: (object.created_at ? Time.parse(object.created_at) : nil),
         
     | 
| 
      
 18 
     | 
    
         
            +
                      updated_at: (object.updated_at ? Time.parse(object.updated_at) : nil)
         
     | 
| 
      
 19 
     | 
    
         
            +
                    }
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                    case attributes[:status]
         
     | 
| 
      
 22 
     | 
    
         
            +
                    when "cancelled"
         
     | 
| 
      
 23 
     | 
    
         
            +
                      # Remove payment methods since customer cannot be reused after cancelling
         
     | 
| 
      
 24 
     | 
    
         
            +
                      Pay::PaymentMethod.where(customer_id: object.customer_id).destroy_all
         
     | 
| 
      
 25 
     | 
    
         
            +
                    when "on_trial"
         
     | 
| 
      
 26 
     | 
    
         
            +
                      attributes[:trial_ends_at] = Time.parse(object.trial_ends_at)
         
     | 
| 
      
 27 
     | 
    
         
            +
                    when "paused"
         
     | 
| 
      
 28 
     | 
    
         
            +
                      # attributes[:pause_starts_at] = Time.parse(object.paused_at)
         
     | 
| 
      
 29 
     | 
    
         
            +
                    when "active", "past_due"
         
     | 
| 
      
 30 
     | 
    
         
            +
                      attributes[:trial_ends_at] = nil
         
     | 
| 
      
 31 
     | 
    
         
            +
                      attributes[:pause_starts_at] = nil
         
     | 
| 
      
 32 
     | 
    
         
            +
                      attributes[:ends_at] = nil
         
     | 
| 
      
 33 
     | 
    
         
            +
                    end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                    # Update or create the subscription
         
     | 
| 
      
 36 
     | 
    
         
            +
                    if (pay_subscription = pay_customer.subscriptions.find_by(processor_id: object.id))
         
     | 
| 
      
 37 
     | 
    
         
            +
                      pay_subscription.with_lock do
         
     | 
| 
      
 38 
     | 
    
         
            +
                        pay_subscription.update!(attributes)
         
     | 
| 
      
 39 
     | 
    
         
            +
                      end
         
     | 
| 
      
 40 
     | 
    
         
            +
                      pay_subscription
         
     | 
| 
      
 41 
     | 
    
         
            +
                    else
         
     | 
| 
      
 42 
     | 
    
         
            +
                      pay_customer.subscriptions.create!(attributes.merge(name: name, processor_id: object.id))
         
     | 
| 
      
 43 
     | 
    
         
            +
                    end
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                  def api_record(**options)
         
     | 
| 
      
 47 
     | 
    
         
            +
                    @api_record ||= ::LemonSqueezy::Subscription.retrieve(id: processor_id)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  rescue ::LemonSqueezy::Error => e
         
     | 
| 
      
 49 
     | 
    
         
            +
                    raise Pay::LemonSqueezy::Error, e
         
     | 
| 
      
 50 
     | 
    
         
            +
                  end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                  def portal_url
         
     | 
| 
      
 53 
     | 
    
         
            +
                    api_record.urls.customer_portal
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                  def update_url
         
     | 
| 
      
 57 
     | 
    
         
            +
                    api_record.urls.update_payment_method
         
     | 
| 
      
 58 
     | 
    
         
            +
                  end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                  def cancel(**options)
         
     | 
| 
      
 61 
     | 
    
         
            +
                    return if canceled?
         
     | 
| 
      
 62 
     | 
    
         
            +
                    response = ::LemonSqueezy::Subscription.cancel(id: processor_id)
         
     | 
| 
      
 63 
     | 
    
         
            +
                    update(status: response.status, ends_at: response.ends_at)
         
     | 
| 
      
 64 
     | 
    
         
            +
                  rescue ::LemonSqueezy::Error => e
         
     | 
| 
      
 65 
     | 
    
         
            +
                    raise Pay::LemonSqueezy::Error, e
         
     | 
| 
      
 66 
     | 
    
         
            +
                  end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                  def cancel_now!(**options)
         
     | 
| 
      
 69 
     | 
    
         
            +
                    raise Pay::Error, "Lemon Squeezy does not support cancelling immediately through the API."
         
     | 
| 
      
 70 
     | 
    
         
            +
                  end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                  def change_quantity(quantity, **options)
         
     | 
| 
      
 73 
     | 
    
         
            +
                    subscription_item = api_record.first_subscription_item
         
     | 
| 
      
 74 
     | 
    
         
            +
                    ::LemonSqueezy::SubscriptionItem.update(id: subscription_item.id, quantity: quantity)
         
     | 
| 
      
 75 
     | 
    
         
            +
                    update(quantity: quantity)
         
     | 
| 
      
 76 
     | 
    
         
            +
                  rescue ::LemonSqueezy::Error => e
         
     | 
| 
      
 77 
     | 
    
         
            +
                    raise Pay::LemonSqueezy::Error, e
         
     | 
| 
      
 78 
     | 
    
         
            +
                  end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                  # A subscription could be set to cancel or pause in the future
         
     | 
| 
      
 81 
     | 
    
         
            +
                  # It is considered on grace period until the cancel or pause time begins
         
     | 
| 
      
 82 
     | 
    
         
            +
                  def on_grace_period?
         
     | 
| 
      
 83 
     | 
    
         
            +
                    (canceled? && Time.current < ends_at) || (paused? && pause_starts_at? && Time.current < pause_starts_at)
         
     | 
| 
      
 84 
     | 
    
         
            +
                  end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                  def paused?
         
     | 
| 
      
 87 
     | 
    
         
            +
                    status == "paused"
         
     | 
| 
      
 88 
     | 
    
         
            +
                  end
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                  def pause(**options)
         
     | 
| 
      
 91 
     | 
    
         
            +
                    response = ::LemonSqueezy::Subscription.pause(id: processor_id, **options)
         
     | 
| 
      
 92 
     | 
    
         
            +
                    update!(status: :paused, pause_starts_at: response.pause&.resumes_at)
         
     | 
| 
      
 93 
     | 
    
         
            +
                  rescue ::LemonSqueezy::Error => e
         
     | 
| 
      
 94 
     | 
    
         
            +
                    raise Pay::LemonSqueezy::Error, e
         
     | 
| 
      
 95 
     | 
    
         
            +
                  end
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
                  def resumable?
         
     | 
| 
      
 98 
     | 
    
         
            +
                    paused? || canceled?
         
     | 
| 
      
 99 
     | 
    
         
            +
                  end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                  def resume
         
     | 
| 
      
 102 
     | 
    
         
            +
                    unless resumable?
         
     | 
| 
      
 103 
     | 
    
         
            +
                      raise StandardError, "You can only resume paused or cancelled subscriptions"
         
     | 
| 
      
 104 
     | 
    
         
            +
                    end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                    if paused? && pause_starts_at? && Time.current < pause_starts_at
         
     | 
| 
      
 107 
     | 
    
         
            +
                      ::LemonSqueezy::Subscription.unpause(id: processor_id)
         
     | 
| 
      
 108 
     | 
    
         
            +
                    else
         
     | 
| 
      
 109 
     | 
    
         
            +
                      ::LemonSqueezy::Subscription.uncancel(id: processor_id)
         
     | 
| 
      
 110 
     | 
    
         
            +
                    end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                    update(ends_at: nil, status: :active, pause_starts_at: nil)
         
     | 
| 
      
 113 
     | 
    
         
            +
                  rescue ::LemonSqueezy::Error => e
         
     | 
| 
      
 114 
     | 
    
         
            +
                    raise Pay::LemonSqueezy::Error, e
         
     | 
| 
      
 115 
     | 
    
         
            +
                  end
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
                  # Lemon Squeezy requires both the Product ID and Variant ID.
         
     | 
| 
      
 118 
     | 
    
         
            +
                  # The Variant ID will be saved as the processor_plan
         
     | 
| 
      
 119 
     | 
    
         
            +
                  def swap(plan, **options)
         
     | 
| 
      
 120 
     | 
    
         
            +
                    raise StandardError, "A plan_id is required to swap a subscription" unless plan
         
     | 
| 
      
 121 
     | 
    
         
            +
                    raise StandardError, "A variant_id is required to swap a subscription" unless options[:variant_id]
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
                    ::LemonSqueezy::Subscription.change_plan id: processor_id, plan_id: plan, variant_id: options[:variant_id]
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                    update(processor_plan: options[:variant_id], ends_at: nil, status: :active)
         
     | 
| 
      
 126 
     | 
    
         
            +
                  end
         
     | 
| 
      
 127 
     | 
    
         
            +
                end
         
     | 
| 
      
 128 
     | 
    
         
            +
              end
         
     | 
| 
      
 129 
     | 
    
         
            +
            end
         
     | 
    
        data/app/models/pay/merchant.rb
    CHANGED
    
    | 
         @@ -6,17 +6,6 @@ module Pay 
     | 
|
| 
       6 
6 
     | 
    
         | 
| 
       7 
7 
     | 
    
         
             
                store_accessor :data, :onboarding_complete
         
     | 
| 
       8 
8 
     | 
    
         | 
| 
       9 
     | 
    
         
            -
                delegate_missing_to :pay_processor
         
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
                def self.pay_processor_for(name)
         
     | 
| 
       12 
     | 
    
         
            -
                  "Pay::#{name.to_s.classify}::Merchant".constantize
         
     | 
| 
       13 
     | 
    
         
            -
                end
         
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
       15 
     | 
    
         
            -
                def pay_processor
         
     | 
| 
       16 
     | 
    
         
            -
                  return if processor.blank?
         
     | 
| 
       17 
     | 
    
         
            -
                  @pay_processor ||= self.class.pay_processor_for(processor).new(self)
         
     | 
| 
       18 
     | 
    
         
            -
                end
         
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
       20 
9 
     | 
    
         
             
                def onboarding_complete?
         
     | 
| 
       21 
10 
     | 
    
         
             
                  ActiveModel::Type::Boolean.new.cast(data&.fetch("onboarding_complete")) || false
         
     | 
| 
       22 
11 
     | 
    
         
             
                end
         
     | 
| 
         @@ -1,13 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module Pay
         
     | 
| 
       2 
2 
     | 
    
         
             
              module PaddleBilling
         
     | 
| 
       3 
     | 
    
         
            -
                class Charge
         
     | 
| 
       4 
     | 
    
         
            -
                   
     | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
       6 
     | 
    
         
            -
                  delegate :processor_id, :customer, to: :pay_charge
         
     | 
| 
       7 
     | 
    
         
            -
             
     | 
| 
       8 
     | 
    
         
            -
                  def initialize(pay_charge)
         
     | 
| 
       9 
     | 
    
         
            -
                    @pay_charge = pay_charge
         
     | 
| 
       10 
     | 
    
         
            -
                  end
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Charge < Pay::Charge
         
     | 
| 
      
 4 
     | 
    
         
            +
                  store_accessor :data, :paddle_receipt_url
         
     | 
| 
       11 
5 
     | 
    
         | 
| 
       12 
6 
     | 
    
         
             
                  def self.sync(charge_id, object: nil, try: 0, retries: 1)
         
     | 
| 
       13 
7 
     | 
    
         
             
                    # Skip loading the latest charge details from the API if we already have it
         
     | 
| 
         @@ -1,20 +1,12 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module Pay
         
     | 
| 
       2 
2 
     | 
    
         
             
              module PaddleBilling
         
     | 
| 
       3 
     | 
    
         
            -
                class  
     | 
| 
       4 
     | 
    
         
            -
                   
     | 
| 
      
 3 
     | 
    
         
            +
                class Customer < Pay::Customer
         
     | 
| 
      
 4 
     | 
    
         
            +
                  has_many :charges, dependent: :destroy, class_name: "Pay::PaddleBilling::Charge"
         
     | 
| 
      
 5 
     | 
    
         
            +
                  has_many :subscriptions, dependent: :destroy, class_name: "Pay::PaddleBilling::Subscription"
         
     | 
| 
      
 6 
     | 
    
         
            +
                  has_many :payment_methods, dependent: :destroy, class_name: "Pay::PaddleBilling::PaymentMethod"
         
     | 
| 
      
 7 
     | 
    
         
            +
                  has_one :default_payment_method, -> { where(default: true) }, class_name: "Pay::PaddleBilling::PaymentMethod"
         
     | 
| 
       5 
8 
     | 
    
         | 
| 
       6 
     | 
    
         
            -
                   
     | 
| 
       7 
     | 
    
         
            -
                    :processor_id?,
         
     | 
| 
       8 
     | 
    
         
            -
                    :email,
         
     | 
| 
       9 
     | 
    
         
            -
                    :customer_name,
         
     | 
| 
       10 
     | 
    
         
            -
                    :card_token,
         
     | 
| 
       11 
     | 
    
         
            -
                    to: :pay_customer
         
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
                  def initialize(pay_customer)
         
     | 
| 
       14 
     | 
    
         
            -
                    @pay_customer = pay_customer
         
     | 
| 
       15 
     | 
    
         
            -
                  end
         
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
       17 
     | 
    
         
            -
                  def customer_attributes
         
     | 
| 
      
 9 
     | 
    
         
            +
                  def api_record_attributes
         
     | 
| 
       18 
10 
     | 
    
         
             
                    {email: email, name: customer_name}
         
     | 
| 
       19 
11 
     | 
    
         
             
                  end
         
     | 
| 
       20 
12 
     | 
    
         | 
| 
         @@ -24,13 +16,16 @@ module Pay 
     | 
|
| 
       24 
16 
     | 
    
         
             
                  # Creates a new Paddle::Customer using `email` and `customer_name` if empty processor_id
         
     | 
| 
       25 
17 
     | 
    
         
             
                  #
         
     | 
| 
       26 
18 
     | 
    
         
             
                  # Returns a Paddle::Customer object
         
     | 
| 
       27 
     | 
    
         
            -
                  def  
     | 
| 
      
 19 
     | 
    
         
            +
                  def api_record
         
     | 
| 
       28 
20 
     | 
    
         
             
                    if processor_id?
         
     | 
| 
       29 
21 
     | 
    
         
             
                      ::Paddle::Customer.retrieve(id: processor_id)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    elsif (pc = ::Paddle::Customer.list(email: email).data&.first)
         
     | 
| 
      
 23 
     | 
    
         
            +
                      update!(processor_id: pc.id)
         
     | 
| 
      
 24 
     | 
    
         
            +
                      pc
         
     | 
| 
       30 
25 
     | 
    
         
             
                    else
         
     | 
| 
       31 
     | 
    
         
            -
                       
     | 
| 
       32 
     | 
    
         
            -
                       
     | 
| 
       33 
     | 
    
         
            -
                       
     | 
| 
      
 26 
     | 
    
         
            +
                      pc = ::Paddle::Customer.create(email: email, name: customer_name)
         
     | 
| 
      
 27 
     | 
    
         
            +
                      update!(processor_id: sc.id)
         
     | 
| 
      
 28 
     | 
    
         
            +
                      pc
         
     | 
| 
       34 
29 
     | 
    
         
             
                    end
         
     | 
| 
       35 
30 
     | 
    
         
             
                  rescue ::Paddle::Error => e
         
     | 
| 
       36 
31 
     | 
    
         
             
                    raise Pay::PaddleBilling::Error, e
         
     | 
| 
         @@ -38,10 +33,9 @@ module Pay 
     | 
|
| 
       38 
33 
     | 
    
         | 
| 
       39 
34 
     | 
    
         
             
                  # Syncs name and email to Paddle::Customer
         
     | 
| 
       40 
35 
     | 
    
         
             
                  # You can also pass in other attributes that will be merged into the default attributes
         
     | 
| 
       41 
     | 
    
         
            -
                  def  
     | 
| 
       42 
     | 
    
         
            -
                     
     | 
| 
       43 
     | 
    
         
            -
                     
     | 
| 
       44 
     | 
    
         
            -
                    ::Paddle::Customer.update(id: processor_id, **attrs)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  def update_api_record(**attributes)
         
     | 
| 
      
 37 
     | 
    
         
            +
                    api_record unless processor_id?
         
     | 
| 
      
 38 
     | 
    
         
            +
                    ::Paddle::Customer.update(id: processor_id, **api_record_attributes.merge(attributes))
         
     | 
| 
       45 
39 
     | 
    
         
             
                  end
         
     | 
| 
       46 
40 
     | 
    
         | 
| 
       47 
41 
     | 
    
         
             
                  def charge(amount, options = {})
         
     | 
| 
         @@ -58,7 +52,7 @@ module Pay 
     | 
|
| 
       58 
52 
     | 
    
         
             
                      metadata: transaction.details.line_items&.first&.id
         
     | 
| 
       59 
53 
     | 
    
         
             
                    }
         
     | 
| 
       60 
54 
     | 
    
         | 
| 
       61 
     | 
    
         
            -
                    charge =  
     | 
| 
      
 55 
     | 
    
         
            +
                    charge = charges.find_or_initialize_by(processor_id: transaction.id)
         
     | 
| 
       62 
56 
     | 
    
         
             
                    charge.update(attrs)
         
     | 
| 
       63 
57 
     | 
    
         
             
                    charge
         
     | 
| 
       64 
58 
     | 
    
         
             
                  rescue ::Paddle::Error => e
         
     | 
| 
         @@ -72,18 +66,7 @@ module Pay 
     | 
|
| 
       72 
66 
     | 
    
         
             
                  # Paddle does not use payment method tokens. The method signature has it here
         
     | 
| 
       73 
67 
     | 
    
         
             
                  # to have a uniform API with the other payment processors.
         
     | 
| 
       74 
68 
     | 
    
         
             
                  def add_payment_method(token = nil, default: true)
         
     | 
| 
       75 
     | 
    
         
            -
                    Pay::PaddleBilling::PaymentMethod.sync(pay_customer:  
     | 
| 
       76 
     | 
    
         
            -
                  end
         
     | 
| 
       77 
     | 
    
         
            -
             
     | 
| 
       78 
     | 
    
         
            -
                  def trial_end_date(subscription)
         
     | 
| 
       79 
     | 
    
         
            -
                    return unless subscription.state == "trialing"
         
     | 
| 
       80 
     | 
    
         
            -
                    Time.zone.parse(subscription.next_payment[:date]).end_of_day
         
     | 
| 
       81 
     | 
    
         
            -
                  end
         
     | 
| 
       82 
     | 
    
         
            -
             
     | 
| 
       83 
     | 
    
         
            -
                  def processor_subscription(subscription_id, options = {})
         
     | 
| 
       84 
     | 
    
         
            -
                    ::Paddle::Subscription.retrieve(id: subscription_id, **options)
         
     | 
| 
       85 
     | 
    
         
            -
                  rescue ::Paddle::Error => e
         
     | 
| 
       86 
     | 
    
         
            -
                    raise Pay::PaddleBilling::Error, e
         
     | 
| 
      
 69 
     | 
    
         
            +
                    Pay::PaddleBilling::PaymentMethod.sync(pay_customer: self)
         
     | 
| 
       87 
70 
     | 
    
         
             
                  end
         
     | 
| 
       88 
71 
     | 
    
         
             
                end
         
     | 
| 
       89 
72 
     | 
    
         
             
              end
         
     | 
| 
         @@ -1,10 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module Pay
         
     | 
| 
       2 
2 
     | 
    
         
             
              module PaddleBilling
         
     | 
| 
       3 
     | 
    
         
            -
                class PaymentMethod
         
     | 
| 
       4 
     | 
    
         
            -
                  attr_reader :pay_payment_method
         
     | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
       6 
     | 
    
         
            -
                  delegate :customer, :processor_id, to: :pay_payment_method
         
     | 
| 
       7 
     | 
    
         
            -
             
     | 
| 
      
 3 
     | 
    
         
            +
                class PaymentMethod < Pay::PaymentMethod
         
     | 
| 
       8 
4 
     | 
    
         
             
                  def self.sync_from_transaction(pay_customer:, transaction:)
         
     | 
| 
       9 
5 
     | 
    
         
             
                    transaction = ::Paddle::Transaction.retrieve(id: transaction)
         
     | 
| 
       10 
6 
     | 
    
         
             
                    return unless transaction.status == "completed"
         
     | 
| 
         @@ -15,7 +11,7 @@ module Pay 
     | 
|
| 
       15 
11 
     | 
    
         
             
                  def self.sync(pay_customer:, attributes:)
         
     | 
| 
       16 
12 
     | 
    
         
             
                    details = attributes.method_details
         
     | 
| 
       17 
13 
     | 
    
         
             
                    attrs = {
         
     | 
| 
       18 
     | 
    
         
            -
                       
     | 
| 
      
 14 
     | 
    
         
            +
                      payment_method_type: details.type.downcase
         
     | 
| 
       19 
15 
     | 
    
         
             
                    }
         
     | 
| 
       20 
16 
     | 
    
         | 
| 
       21 
17 
     | 
    
         
             
                    case details.type.downcase
         
     | 
| 
         @@ -31,15 +27,9 @@ module Pay 
     | 
|
| 
       31 
27 
     | 
    
         
             
                    payment_method
         
     | 
| 
       32 
28 
     | 
    
         
             
                  end
         
     | 
| 
       33 
29 
     | 
    
         | 
| 
       34 
     | 
    
         
            -
                  def initialize(pay_payment_method)
         
     | 
| 
       35 
     | 
    
         
            -
                    @pay_payment_method = pay_payment_method
         
     | 
| 
       36 
     | 
    
         
            -
                  end
         
     | 
| 
       37 
     | 
    
         
            -
             
     | 
| 
       38 
     | 
    
         
            -
                  # Sets payment method as default
         
     | 
| 
       39 
30 
     | 
    
         
             
                  def make_default!
         
     | 
| 
       40 
31 
     | 
    
         
             
                  end
         
     | 
| 
       41 
32 
     | 
    
         | 
| 
       42 
     | 
    
         
            -
                  # Remove payment method
         
     | 
| 
       43 
33 
     | 
    
         
             
                  def detach
         
     | 
| 
       44 
34 
     | 
    
         
             
                  end
         
     | 
| 
       45 
35 
     | 
    
         
             
                end
         
     |