pay-lago 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (140) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/main.yml +27 -0
  3. data/.gitignore +28 -0
  4. data/.rspec +3 -0
  5. data/.standard.yml +3 -0
  6. data/CHANGELOG.md +2 -0
  7. data/CODE_OF_CONDUCT.md +84 -0
  8. data/Gemfile +25 -0
  9. data/Gemfile.lock +292 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +85 -0
  12. data/Rakefile +32 -0
  13. data/app/controllers/pay/webhooks/lago_controller.rb +41 -0
  14. data/app/models/concerns/pay/lago/pay_customer_extensions.rb +15 -0
  15. data/app/models/concerns/pay/lago/pay_extensions.rb +15 -0
  16. data/bin/console +15 -0
  17. data/bin/rails +16 -0
  18. data/bin/setup +8 -0
  19. data/config/routes.rb +5 -0
  20. data/docs/1_customers.md +18 -0
  21. data/docs/2_charges.md +72 -0
  22. data/docs/3_subscriptions.md +67 -0
  23. data/docs/4_webhooks.md +66 -0
  24. data/lib/pay/lago/attributes.rb +24 -0
  25. data/lib/pay/lago/billable.rb +148 -0
  26. data/lib/pay/lago/charge.rb +82 -0
  27. data/lib/pay/lago/engine.rb +27 -0
  28. data/lib/pay/lago/error.rb +9 -0
  29. data/lib/pay/lago/payment_method.rb +37 -0
  30. data/lib/pay/lago/subscription.rb +134 -0
  31. data/lib/pay/lago/version.rb +7 -0
  32. data/lib/pay/lago/webhook_extensions.rb +10 -0
  33. data/lib/pay/lago/webhooks/customer_payment_provider_created.rb +18 -0
  34. data/lib/pay/lago/webhooks/invoice_created.rb +15 -0
  35. data/lib/pay/lago/webhooks/invoice_drafted.rb +11 -0
  36. data/lib/pay/lago/webhooks/invoice_one_off_created.rb +15 -0
  37. data/lib/pay/lago/webhooks/invoice_payment_status_updated.rb +14 -0
  38. data/lib/pay/lago/webhooks/subscription_started.rb +12 -0
  39. data/lib/pay/lago/webhooks/subscription_terminated.rb +12 -0
  40. data/lib/pay/lago.rb +91 -0
  41. data/pay-lago.gemspec +27 -0
  42. data/sig/pay/lago.rbs +6 -0
  43. data/test/controllers/pay/webhooks/lago_controller_test.rb +37 -0
  44. data/test/dummy/.browserslistrc +1 -0
  45. data/test/dummy/Rakefile +6 -0
  46. data/test/dummy/app/assets/config/manifest.js +4 -0
  47. data/test/dummy/app/assets/images/.keep +0 -0
  48. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  49. data/test/dummy/app/channels/application_cable/channel.rb +4 -0
  50. data/test/dummy/app/channels/application_cable/connection.rb +4 -0
  51. data/test/dummy/app/controllers/application_controller.rb +4 -0
  52. data/test/dummy/app/controllers/concerns/.keep +0 -0
  53. data/test/dummy/app/controllers/main_controller.rb +4 -0
  54. data/test/dummy/app/controllers/payment_methods_controller.rb +5 -0
  55. data/test/dummy/app/helpers/application_helper.rb +2 -0
  56. data/test/dummy/app/helpers/current_helper.rb +9 -0
  57. data/test/dummy/app/javascript/application.js +9 -0
  58. data/test/dummy/app/javascript/controllers/application.js +10 -0
  59. data/test/dummy/app/javascript/controllers/index.js +11 -0
  60. data/test/dummy/app/jobs/application_job.rb +2 -0
  61. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  62. data/test/dummy/app/models/account.rb +3 -0
  63. data/test/dummy/app/models/application_record.rb +3 -0
  64. data/test/dummy/app/models/concerns/.keep +0 -0
  65. data/test/dummy/app/models/team.rb +10 -0
  66. data/test/dummy/app/models/user.rb +3 -0
  67. data/test/dummy/app/views/layouts/application.html.erb +36 -0
  68. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  69. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  70. data/test/dummy/app/webhooks/charge_succeeded.rb +5 -0
  71. data/test/dummy/bin/importmap +4 -0
  72. data/test/dummy/bin/rails +4 -0
  73. data/test/dummy/bin/rake +4 -0
  74. data/test/dummy/bin/setup +33 -0
  75. data/test/dummy/bin/webpack +18 -0
  76. data/test/dummy/bin/webpack-dev-server +18 -0
  77. data/test/dummy/config/application.rb +25 -0
  78. data/test/dummy/config/boot.rb +6 -0
  79. data/test/dummy/config/cable.yml +9 -0
  80. data/test/dummy/config/database.yml +3 -0
  81. data/test/dummy/config/environment.rb +5 -0
  82. data/test/dummy/config/environments/development.rb +70 -0
  83. data/test/dummy/config/environments/production.rb +86 -0
  84. data/test/dummy/config/environments/test.rb +60 -0
  85. data/test/dummy/config/importmap.rb +8 -0
  86. data/test/dummy/config/initializers/application_controller_renderer.rb +6 -0
  87. data/test/dummy/config/initializers/assets.rb +11 -0
  88. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  89. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  90. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  91. data/test/dummy/config/initializers/inflections.rb +16 -0
  92. data/test/dummy/config/initializers/mime_types.rb +4 -0
  93. data/test/dummy/config/initializers/pay.rb +11 -0
  94. data/test/dummy/config/initializers/session_store.rb +3 -0
  95. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  96. data/test/dummy/config/locales/en.yml +23 -0
  97. data/test/dummy/config/puma.rb +47 -0
  98. data/test/dummy/config/routes.rb +4 -0
  99. data/test/dummy/config/secrets.yml +22 -0
  100. data/test/dummy/config/spring.rb +6 -0
  101. data/test/dummy/config/storage.yml +34 -0
  102. data/test/dummy/config.ru +6 -0
  103. data/test/dummy/db/migrate/20170205000000_create_users.rb +22 -0
  104. data/test/dummy/db/schema.rb +125 -0
  105. data/test/dummy/lib/assets/.keep +0 -0
  106. data/test/dummy/log/.keep +0 -0
  107. data/test/dummy/public/404.html +67 -0
  108. data/test/dummy/public/422.html +67 -0
  109. data/test/dummy/public/500.html +66 -0
  110. data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
  111. data/test/dummy/public/apple-touch-icon.png +0 -0
  112. data/test/dummy/public/favicon.ico +0 -0
  113. data/test/dummy/vendor/javascript/.keep +0 -0
  114. data/test/fixtures/accounts.yml +2 -0
  115. data/test/fixtures/pay/customers.yml +7 -0
  116. data/test/fixtures/teams.yml +4 -0
  117. data/test/fixtures/users.yml +11 -0
  118. data/test/pay/lago/billable_test.rb +42 -0
  119. data/test/pay/lago/charge_test.rb +23 -0
  120. data/test/pay/lago/subscription_test.rb +48 -0
  121. data/test/support/fixtures/lago/invoice.one_off_created.json +120 -0
  122. data/test/support/vcr.rb +28 -0
  123. data/test/test_helper.rb +66 -0
  124. data/test/vcr_cassettes/test_add_payment_method_to_lago.yml +56 -0
  125. data/test/vcr_cassettes/test_can_get_lago_processor_customer.yml +56 -0
  126. data/test/vcr_cassettes/test_can_make_a_lago_processor_charge.yml +163 -0
  127. data/test/vcr_cassettes/test_generates_lago_processor_id.yml +107 -0
  128. data/test/vcr_cassettes/test_lago_cannot_change_quantity.yml +108 -0
  129. data/test/vcr_cassettes/test_lago_processor_cancel.yml +212 -0
  130. data/test/vcr_cassettes/test_lago_processor_charge.yml +325 -0
  131. data/test/vcr_cassettes/test_lago_processor_on_grace_period__is_always_false.yml +108 -0
  132. data/test/vcr_cassettes/test_lago_processor_pause_raises_an_error.yml +108 -0
  133. data/test/vcr_cassettes/test_lago_processor_paused__is_always_false.yml +108 -0
  134. data/test/vcr_cassettes/test_lago_processor_refund_with_premium.yml +378 -0
  135. data/test/vcr_cassettes/test_lago_processor_refund_without_premium.yml +375 -0
  136. data/test/vcr_cassettes/test_lago_processor_resume_raises_an_error.yml +108 -0
  137. data/test/vcr_cassettes/test_lago_processor_subscribe.yml +108 -0
  138. data/test/vcr_cassettes/test_lago_processor_subscription.yml +160 -0
  139. data/test/vcr_cassettes/test_lago_processor_swap.yml +212 -0
  140. metadata +212 -0
@@ -0,0 +1,134 @@
1
+ module Pay
2
+ module Lago
3
+ class Subscription
4
+ STATUS_MAP = {
5
+ "pending" => "active",
6
+ "terminated" => "canceled"
7
+ }
8
+ attr_reader :pay_subscription
9
+
10
+ delegate :active?,
11
+ :canceled?,
12
+ :on_grace_period?,
13
+ :on_trial?,
14
+ :ends_at,
15
+ :ends_at?,
16
+ :owner,
17
+ :processor_subscription,
18
+ :processor_id,
19
+ :prorate,
20
+ :processor_plan,
21
+ :quantity?,
22
+ :quantity,
23
+ to: :pay_subscription
24
+
25
+ def self.sync(customer_id, subscription_id, object: nil, try: 0, retries: 1)
26
+ object ||= get_subscription(customer_id, subscription_id)
27
+
28
+ pay_customer = Pay::Customer.find_by(processor: :lago, processor_id: customer_id)
29
+ if pay_customer.blank?
30
+ Rails.logger.debug "Pay::Customer #{object.customer} is not in the database while syncing Lago Subscription #{object.lago_id}"
31
+ return
32
+ end
33
+
34
+ attrs = {
35
+ name: object.name.present? ? object.name : Pay.default_plan_name,
36
+ processor_id: object.external_id,
37
+ processor_plan: object.plan_code,
38
+ ends_at: object.cancelled_at || object.terminated_at,
39
+ quantity: 1,
40
+ status: STATUS_MAP.fetch(object.status, object.status),
41
+ data: Lago.openstruct_to_h(object)
42
+ }
43
+
44
+ # Update or create the charge
45
+ if (pay_subscription = pay_customer.subscriptions.find_by(processor_id: subscription_id))
46
+ pay_subscription.with_lock do
47
+ pay_subscription.update!(attrs)
48
+ end
49
+ pay_subscription
50
+ else
51
+ pay_customer.subscriptions.create!(attrs)
52
+ end
53
+ rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
54
+ try += 1
55
+ if try <= retries
56
+ sleep 0.1
57
+ retry
58
+ else
59
+ raise
60
+ end
61
+ end
62
+
63
+ def self.get_subscription(customer_id, external_id)
64
+ result = Lago.client.subscriptions.get_all(external_customer_id: customer_id, per_page: 9223372036854775807)
65
+ result.subscriptions.each { |sub| return sub if sub.external_id == external_id }
66
+ raise Pay::Lago::Error.new("No subscription could be found.")
67
+ rescue ::Lago::Api::HttpError => e
68
+ raise Pay::Lago::Error, e
69
+ end
70
+
71
+ def initialize(pay_subscription)
72
+ @pay_subscription = pay_subscription
73
+ end
74
+
75
+ def subscription
76
+ @lago_subscription ||= self.class.get_subscription(pay_subscription.customer.processor_id, processor_id)
77
+ end
78
+
79
+ def cancel(**options)
80
+ customer_id = subscription.external_customer_id
81
+ response = Lago.client.subscriptions.destroy(URI.encode_www_form_component(processor_id), options: options)
82
+ self.class.sync(customer_id, processor_id, object: response)
83
+ rescue ::Lago::Api::HttpError => e
84
+ raise Pay::Lago::Error, e
85
+ end
86
+
87
+ def cancel_now!(**options)
88
+ cancel(options)
89
+ end
90
+
91
+ def change_quantity(_quantity, **_options)
92
+ raise Pay::Lago::Error.new("Lago subscriptions do not implement quantity.")
93
+ end
94
+
95
+ def on_grace_period?
96
+ false
97
+ end
98
+
99
+ def paused?
100
+ false
101
+ end
102
+
103
+ def changing_plan?
104
+ return false unless subscription.downgrade_plan_date
105
+ subscription.downgrade_plan_date > Time.now
106
+ end
107
+
108
+ def pause
109
+ raise Pay::Lago::Error.new("Lago subscriptions cannot be paused.")
110
+ end
111
+
112
+ def resume
113
+ raise Pay::Lago::Error.new("Lago subscriptions cannot be paused.")
114
+ end
115
+
116
+ def swap(plan, **options)
117
+ customer_id = subscription.external_customer_id
118
+ attributes = options.merge(
119
+ external_customer_id: customer_id,
120
+ external_id: processor_id, plan_code: plan
121
+ )
122
+ new_subscription = Lago.client.subscriptions.create(attributes)
123
+ self.class.sync(customer_id, processor_id, object: new_subscription)
124
+ rescue ::Lago::Api::HttpError => e
125
+ raise Pay::Lago::Error, e
126
+ end
127
+
128
+ # Retries the latest invoice for a Past Due subscription
129
+ def retry_failed_payment
130
+ raise Pay::Lago::Error.new("Lago subscriptions cannot be retried.")
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pay
4
+ module Lago
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ module Pay
2
+ module Lago
3
+ module WebhookExtensions
4
+ def rehydrated_event
5
+ return to_recursive_ostruct(event) if processor == "lago"
6
+ super
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,18 @@
1
+ module Pay
2
+ module Lago
3
+ module Webhooks
4
+ class CustomerPaymentProviderCreated
5
+ def call(event)
6
+ customer = event.customer
7
+ pay_customer = Pay::Customer.find_by(processor_id: customer.external_id)
8
+ unless pay_customer.present?
9
+ Rails.logger.debug "Pay::Customer #{customer.external_id} is not in the database while syncing Payment Information"
10
+ return false
11
+ end
12
+
13
+ pay_customer.update!(data: Pay::Lago.openstruct_to_h(customer))
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ module Pay
2
+ module Lago
3
+ module Webhooks
4
+ class InvoiceCreated
5
+ def call(event)
6
+ pay_charge = Pay::Lago::Charge.sync(event.invoice.lago_id, object: event.invoice)
7
+
8
+ if pay_charge && Pay.send_email?(:receipt, pay_charge)
9
+ Pay.mailer.with(pay_customer: pay_charge.customer, pay_charge: pay_charge).receipt.deliver_later
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module Pay
2
+ module Lago
3
+ module Webhooks
4
+ class InvoiceDrafted
5
+ def call(event)
6
+ Pay::Lago::Charge.sync(event.invoice.lago_id, object: event.invoice)
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module Pay
2
+ module Lago
3
+ module Webhooks
4
+ class InvoiceOneOffCreated
5
+ def call(event)
6
+ pay_charge = Pay::Lago::Charge.sync(event.invoice.lago_id, object: event.invoice)
7
+
8
+ if pay_charge && Pay.send_email?(:receipt, pay_charge)
9
+ Pay.mailer.with(pay_customer: pay_charge.customer, pay_charge: pay_charge).receipt.deliver_later
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ module Pay
2
+ module Lago
3
+ module Webhooks
4
+ class InvoicePaymentStatusUpdated
5
+ def call(event)
6
+ charge = Pay::Charge.find_by_processor_and_id(:lago, event.invoice.lago_id)
7
+ return unless charge.present?
8
+ charge.data.merge!(payment_status: event.invoice.payment_status)
9
+ charge.save!
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ module Pay
2
+ module Lago
3
+ module Webhooks
4
+ class SubscriptionStarted
5
+ def call(event)
6
+ subscription = event.subscription
7
+ Pay::Lago::Subscription.sync(subscription.external_customer_id, subscription.external_id, object: subscription)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Pay
2
+ module Lago
3
+ module Webhooks
4
+ class SubscriptionTerminated
5
+ def call(event)
6
+ subscription = event.subscription
7
+ Pay::Lago::Subscription.sync(subscription.external_customer_id, subscription.external_id, object: subscription)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
data/lib/pay/lago.rb ADDED
@@ -0,0 +1,91 @@
1
+ require "pay/lago/version"
2
+ require "pay/lago/engine"
3
+
4
+ require "lago-ruby-client"
5
+
6
+ require "action_mailer"
7
+ require "active_support"
8
+
9
+ module Pay
10
+ module Lago
11
+ autoload :Attributes, "pay/lago/attributes"
12
+ autoload :Billable, "pay/lago/billable"
13
+ autoload :Charge, "pay/lago/charge"
14
+ autoload :Error, "pay/lago/error"
15
+ autoload :PaymentMethod, "pay/lago/payment_method"
16
+ autoload :Subscription, "pay/lago/subscription"
17
+ autoload :WebhookExtensions, "pay/lago/webhook_extensions"
18
+
19
+ module Webhooks
20
+ autoload :CustomerPaymentProviderCreated, "pay/lago/webhooks/customer_payment_provider_created"
21
+ autoload :InvoiceCreated, "pay/lago/webhooks/invoice_created"
22
+ autoload :InvoiceDrafted, "pay/lago/webhooks/invoice_drafted"
23
+ autoload :InvoiceOneOffCreated, "pay/lago/webhooks/invoice_one_off_created"
24
+ autoload :InvoicePaymentStatusUpdated, "pay/lago/webhooks/invoice_payment_status_updated"
25
+ autoload :SubscriptionStarted, "pay/lago/webhooks/subscription_started"
26
+ autoload :SubscriptionTerminated, "pay/lago/webhooks/subscription_terminated"
27
+ end
28
+
29
+ extend Pay::Env
30
+
31
+ class << self
32
+ def enabled?
33
+ defined?(::Lago)
34
+ end
35
+
36
+ def client
37
+ @client ||= ::Lago::Api::Client.new(api_key: api_key, api_url: api_url)
38
+ end
39
+
40
+ def valid_auth?
41
+ webhook_public_key
42
+ true
43
+ rescue
44
+ false
45
+ end
46
+
47
+ def create_webhook!(url, mount = Pay.routes_path)
48
+ uri = URI(url)
49
+ raise Pay::Lago::Error.new("Invalid URI: #{uri}") unless uri.host.present?
50
+ uri.scheme = "https" unless %w[http https].include?(uri.scheme)
51
+ uri.path = "/#{mount}/webhooks/lago".squeeze("/")
52
+ client.webhook_endpoints.create(signature_algo: "jwt", webhook_url: uri.to_s)
53
+ true
54
+ rescue ::Lago::Api::HttpError => e
55
+ return true if e.error_code == 422
56
+ raise Pay::Lago::Error, e
57
+ end
58
+
59
+ def webhook_public_key
60
+ @webhook_public_key ||= client.webhooks.public_key
61
+ end
62
+
63
+ def api_key
64
+ find_value_by_name(:lago, :api_key)
65
+ end
66
+
67
+ def api_url
68
+ find_value_by_name(:lago, :api_url)
69
+ end
70
+
71
+ def configure_webhooks
72
+ Pay::Webhooks.configure do |events|
73
+ events.subscribe "lago.customer.payment_provider_created", Pay::Lago::Webhooks::CustomerPaymentProviderCreated.new
74
+ events.subscribe "lago.invoice.created", Pay::Lago::Webhooks::InvoiceCreated.new
75
+ events.subscribe "lago.invoice.drafted", Pay::Lago::Webhooks::InvoiceDrafted.new
76
+ events.subscribe "lago.invoice.one_off_created", Pay::Lago::Webhooks::InvoiceOneOffCreated.new
77
+ events.subscribe "lago.invoice.payment_status_updated", Pay::Lago::Webhooks::InvoicePaymentStatusUpdated.new
78
+ events.subscribe "lago.subscription.started", Pay::Lago::Webhooks::SubscriptionStarted.new
79
+ events.subscribe "lago.subscription.terminated", Pay::Lago::Webhooks::SubscriptionTerminated.new
80
+ end
81
+ end
82
+
83
+ # Helpers for working with Lago client
84
+ def openstruct_to_h(ostruct)
85
+ return ostruct.to_h.transform_values { |value| openstruct_to_h(value) } if ostruct.is_a?(OpenStruct) || ostruct.is_a?(Hash)
86
+ return ostruct.map { |value| openstruct_to_h(value) } if ostruct.is_a?(Array)
87
+ ostruct
88
+ end
89
+ end
90
+ end
91
+ end
data/pay-lago.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ require_relative "lib/pay/lago/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "pay-lago"
5
+ spec.version = Pay::Lago::VERSION
6
+ spec.authors = ["Moxvallix"]
7
+ spec.email = ["dev@moxvallix.com"]
8
+
9
+ spec.summary = "Lago processor support for Pay gem."
10
+ spec.homepage = "https://github.com/demingfactor/pay-lago"
11
+ spec.license = "MIT"
12
+
13
+ spec.metadata["homepage_uri"] = spec.homepage
14
+ spec.metadata["source_code_uri"] = "https://github.com/demingfactor/pay-lago"
15
+ spec.metadata["changelog_uri"] = "https://github.com/demingfactor/pay-lago/blob/main/CHANGELOG.md"
16
+
17
+ # Specify which files should be added to the gem when it is released.
18
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
+ spec.files = `git ls-files -z`.split("\u0000")
20
+
21
+ # Uncomment to register a new dependency of your gem
22
+ spec.add_dependency "pay", "~> 6.8"
23
+ spec.add_dependency "lago-ruby-client", "0.50.0-beta"
24
+
25
+ # For more information and examples about making a new gem, check out our
26
+ # guide at: https://bundler.io/guides/creating_gem.html
27
+ end
data/sig/pay/lago.rbs ADDED
@@ -0,0 +1,6 @@
1
+ module Pay
2
+ module Lago
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
@@ -0,0 +1,37 @@
1
+ # require "test_helper"
2
+
3
+ module Pay
4
+ class LagoWebhooksControllerTest < ActionDispatch::IntegrationTest
5
+ include Pay::Engine.routes.url_helpers
6
+
7
+ setup do
8
+ @routes = Lago::Engine.routes
9
+ end
10
+
11
+ test "should handle lago post requests" do
12
+ post webhooks_lago_path
13
+ assert_response :bad_request
14
+ end
15
+
16
+ test "should parse a lago webhook" do
17
+ lago_event = fake_event("lago/invoice.one_off_created")
18
+ Pay::Webhooks::LagoController.any_instance.expects(:verified_event).returns(lago_event)
19
+
20
+ pay_customer = pay_customers(:lago)
21
+ pay_customer.update(processor_id: lago_event.dig("invoice", "customer", "external_id"))
22
+
23
+ assert_difference "Pay::Webhook.count" do
24
+ assert_enqueued_with(job: Pay::Webhooks::ProcessJob) do
25
+ post webhooks_lago_path, params: lago_event
26
+ assert_response :success
27
+ end
28
+ end
29
+
30
+ assert_difference "Pay::Charge.count" do
31
+ perform_enqueued_jobs
32
+ end
33
+ end
34
+ end
35
+ module Lago
36
+ end
37
+ end
@@ -0,0 +1 @@
1
+ defaults
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require_relative "config/application"
5
+
6
+ Rails.application.load_tasks
@@ -0,0 +1,4 @@
1
+ //= link_tree ../images
2
+ //= link_directory ../stylesheets .css
3
+ //= link_tree ../../javascript .js
4
+ //= link_tree ../../../vendor/javascript .js
File without changes
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,4 @@
1
+ module ApplicationCable
2
+ class Channel < ActionCable::Channel::Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module ApplicationCable
2
+ class Connection < ActionCable::Connection::Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ class ApplicationController < ActionController::Base
2
+ include CurrentHelper
3
+ protect_from_forgery with: :exception
4
+ end
File without changes
@@ -0,0 +1,4 @@
1
+ class MainController < ApplicationController
2
+ def show
3
+ end
4
+ end
@@ -0,0 +1,5 @@
1
+ class PaymentMethodsController < ApplicationController
2
+ def show
3
+ @payment_method = current_user.payment_processor&.default_payment_method
4
+ end
5
+ end
@@ -0,0 +1,2 @@
1
+ module ApplicationHelper
2
+ end
@@ -0,0 +1,9 @@
1
+ module CurrentHelper
2
+ def current_user
3
+ @current_user ||= User.first || User.create!(email: "test@user.com", first_name: "Test", last_name: "User")
4
+ end
5
+
6
+ def current_team
7
+ @current_user ||= Team.first || Team.create!(name: "Test Team", email: "test@team.com", owner: current_user)
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
2
+ import "@hotwired/turbo-rails"
3
+ import "controllers"
4
+
5
+ // Bootstrap
6
+ document.addEventListener("turbo:load", function() {
7
+ $('[data-toggle="tooltip"]').tooltip()
8
+ $('[data-toggle="popover"]').popover()
9
+ })
@@ -0,0 +1,10 @@
1
+ import { Application } from "@hotwired/stimulus"
2
+
3
+ const application = Application.start()
4
+
5
+ // Configure Stimulus development experience
6
+ application.debug = false
7
+ window.Stimulus = application
8
+
9
+ export { application }
10
+
@@ -0,0 +1,11 @@
1
+ // Import and register all your controllers from the importmap under controllers/*
2
+
3
+ import { application } from "controllers/application"
4
+
5
+ // Eager load all controllers defined in the import map under controllers/**/*_controller
6
+ import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
7
+ eagerLoadControllersFrom("controllers", application)
8
+
9
+ // Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
10
+ // import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
11
+ // lazyLoadControllersFrom("controllers", application)
@@ -0,0 +1,2 @@
1
+ class ApplicationJob < ActiveJob::Base
2
+ end
@@ -0,0 +1,4 @@
1
+ class ApplicationMailer < ActionMailer::Base
2
+ default from: "from@example.com"
3
+ layout "mailer"
4
+ end
@@ -0,0 +1,3 @@
1
+ class Account < ApplicationRecord
2
+ pay_merchant
3
+ end
@@ -0,0 +1,3 @@
1
+ class ApplicationRecord < ActiveRecord::Base
2
+ self.abstract_class = true
3
+ end
File without changes
@@ -0,0 +1,10 @@
1
+ class Team < ApplicationRecord
2
+ pay_customer
3
+ pay_merchant
4
+
5
+ belongs_to :owner, polymorphic: true
6
+
7
+ def email
8
+ owner.email
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ class User < ApplicationRecord
2
+ pay_customer
3
+ end
@@ -0,0 +1,36 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Dummy</title>
5
+ <%= csrf_meta_tags %>
6
+
7
+ <%# Bootstrap %>
8
+ <link rel="stylesheet" href="https://unpkg.com/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
9
+ <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
10
+ <script src="https://unpkg.com/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx" crossorigin="anonymous"></script>
11
+
12
+ <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
13
+ <%= javascript_importmap_tags %>
14
+ </head>
15
+
16
+ <body>
17
+ <nav class="navbar navbar-expand-lg navbar-light bg-light">
18
+ <%= link_to "Pay Examples", root_path, class: "navbar-brand" %>
19
+ <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
20
+ <span class="navbar-toggler-icon"></span>
21
+ </button>
22
+ </nav>
23
+
24
+ <div class="container mx-auto">
25
+ <% if notice %>
26
+ <div class="alert alert-notice"><%= notice %></div>
27
+ <% end %>
28
+
29
+ <% if alert %>
30
+ <div class="alert alert-warning"><%= alert %></div>
31
+ <% end %>
32
+
33
+ <%= yield %>
34
+ </div>
35
+ </body>
36
+ </html>
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5
+ <style>
6
+ /* Email styles need to be inline */
7
+ </style>
8
+ </head>
9
+
10
+ <body>
11
+ <%= yield %>
12
+ </body>
13
+ </html>
@@ -0,0 +1 @@
1
+ <%= yield %>
@@ -0,0 +1,5 @@
1
+ class ChargeSucceeded
2
+ def call(event)
3
+ Rails.logger.debug event
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../config/application"
4
+ require "importmap/commands"
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ APP_PATH = File.expand_path('../config/application', __dir__)
3
+ require_relative '../config/boot'
4
+ require 'rails/commands'