vindi-rails-integrations 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8dd230e91c45c3972312a8c56b152b87b6cd5160cd306456cdf3404ce90b74b9
4
- data.tar.gz: 9b0cb8eec04493f2f3547c6d8a05b8b409c70ff18d42e1e9d53c99dfa781e1be
3
+ metadata.gz: ac62896e691574d5b96e6ed7b00e2456222db8b7d575393199e1e5d2f982aaaf
4
+ data.tar.gz: 95876321b904170ac20f12163240fa140fecf3747f3383e725220b16fdf8b7e9
5
5
  SHA512:
6
- metadata.gz: 43fabc5a71557ac8ca880d9d7be71cea5908a6c9d76b24e94d28feddc1720748a649afecd0202434c77403e11c2b8587114a3983be330162e8b72a11bc2cb915
7
- data.tar.gz: b92a4cf06a918e309e842ca9de25aee365c7b477455bbdb4646d30963bf45cdfa5dfad425d4b2c9aa97403b6a5adcb5cc994b609689c3b390235918f6c4cd250
6
+ metadata.gz: 7dfcb70a8753f6a6272b82d4e8f71d23542a4400e45b4b5860b9291aeef90424ffe5178d1e47e7c2da3d06178b90f8ef086da7dbe067ef9213650d2805f288f9
7
+ data.tar.gz: 9d1ab6461d8d9e41e8825b6c1d4fc521496c80ee68dca3d27d76ac04f99e30de7e3fad58b10ed156011baa37aa423e877c7fd6eabe33493202e7afb476892aa7
data/README.md CHANGED
@@ -1,48 +1,59 @@
1
- # Vindi Rails Integrations
2
-
3
- [Leia em Português (README.pt-BR.md)](./README.pt-BR.md)
4
-
5
- An extension gem for the [vindi-rails](https://github.com/wesleyskap/vindi-rails) core SDK, providing backend integrations such as automatic ActiveRecord model synchronization, webhook controller endpoints, asynchronous processing jobs, and verification Rake tasks.
6
-
7
- ## Installation
8
-
9
- Add this line to your application's Gemfile:
10
-
11
- ```ruby
12
- gem 'vindi-rails-integrations'
13
- ```
14
-
15
- ## Features & Usage
16
-
17
- ### 1. Webhook Setup
18
- To handle incoming webhook events asynchronously with built-in access token verification:
19
- ```bash
20
- bundle exec rails generate vindi:webhook
21
- ```
22
- This generates:
23
- - `Vindi::WebhooksController` (`app/controllers/vindi/webhooks_controller.rb`)
24
- - `Vindi::WebhookJob` (`app/jobs/vindi/webhook_job.rb`)
25
-
26
- Configure your webhook access token in your environment files:
27
- ```bash
28
- ENV["VINDI_WEBHOOK_TOKEN"] = "YOUR_SECURE_TOKEN"
29
- ```
30
-
31
- ### 2. ActiveRecord Model Sync
32
- To automatically synchronize local models (e.g. `User`) with Vindi Customers:
33
- ```bash
34
- bundle exec rails generate vindi:sync User
35
- ```
36
- This generates a database migration to add `vindi_customer_id` and includes the `Vindi::Synchronizable` concern into your model.
37
-
38
- ### 3. Rake Tasks
39
- - **`bundle exec rake vindi:status`**: Verifies API configuration, environment, credentials (safely masked), and tests connection to Vindi.
40
- - **`bundle exec rake vindi:audit model=User`**: Reconciles database records against the Vindi API to detect missing or mismatched records.
41
- - **`bundle exec rake vindi:test_webhook event=bill_paid`**: Simulates sending a webhook event payload directly to your local endpoint.
42
-
43
- ## Running Tests
44
-
45
- To run the Minitest suite:
46
- ```bash
47
- bundle exec rake test
48
- ```
1
+ # Vindi Rails Integrations
2
+
3
+ [Leia em Português (README.pt-BR.md)](./README.pt-BR.md)
4
+
5
+ An extension gem for the [vindi-rails](https://github.com/wesleyskap/vindi-rails) core SDK, providing backend integrations such as automatic ActiveRecord model synchronization, webhook controller endpoints, asynchronous processing jobs, and verification Rake tasks.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'vindi-rails-integrations'
13
+ ```
14
+
15
+ ## Features & Usage
16
+
17
+ ### 1. Webhook Setup
18
+ To handle incoming webhook events asynchronously with built-in access token verification:
19
+ ```bash
20
+ bundle exec rails generate vindi:webhook
21
+ ```
22
+ This generates:
23
+ - `Vindi::WebhooksController` (`app/controllers/vindi/webhooks_controller.rb`)
24
+ - `Vindi::WebhookJob` (`app/jobs/vindi/webhook_job.rb`)
25
+
26
+ Configure your webhook access token in your environment files:
27
+ ```bash
28
+ ENV["VINDI_WEBHOOK_TOKEN"] = "YOUR_SECURE_TOKEN"
29
+ ```
30
+
31
+ #### Modular Webhook Handlers
32
+ Instead of processing all webhook events inside a single `WebhookJob`, you can generate modular event-specific handlers:
33
+ ```bash
34
+ bundle exec rails generate vindi:webhook_handler subscription_canceled
35
+ ```
36
+ This generates:
37
+ - `Vindi::Webhooks::BaseHandler` (`app/services/vindi/webhooks/base_handler.rb`) - created once if missing.
38
+ - `Vindi::Webhooks::SubscriptionCanceledHandler` (`app/services/vindi/webhooks/subscription_canceled_handler.rb`)
39
+
40
+ The main `WebhookJob` automatically detects and forwards the event payload to matching handlers (e.g. `Vindi::Webhooks::SubscriptionCanceledHandler` for `subscription_canceled` events) with safe fallback to legacy inline handlers.
41
+
42
+ ### 2. ActiveRecord Model Sync
43
+ To automatically synchronize local models (e.g. `User`) with Vindi Customers:
44
+ ```bash
45
+ bundle exec rails generate vindi:sync User
46
+ ```
47
+ This generates a database migration to add `vindi_customer_id` and includes the `Vindi::Synchronizable` concern into your model.
48
+
49
+ ### 3. Rake Tasks
50
+ - **`bundle exec rake vindi:status`**: Verifies API configuration, environment, credentials (safely masked), and tests connection to Vindi.
51
+ - **`bundle exec rake vindi:audit model=User`**: Reconciles database records against the Vindi API to detect missing or mismatched records.
52
+ - **`bundle exec rake vindi:test_webhook event=bill_paid`**: Simulates sending a webhook event payload directly to your local endpoint.
53
+
54
+ ## Running Tests
55
+
56
+ To run the Minitest suite:
57
+ ```bash
58
+ bundle exec rake test
59
+ ```
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vindi
4
+ module Webhooks
5
+ class BaseHandler
6
+ attr_reader :event_payload
7
+
8
+ def initialize(event_payload)
9
+ @event_payload = event_payload
10
+ end
11
+
12
+ def call
13
+ raise NotImplementedError, "#{self.class} must implement #call"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vindi
4
+ module Webhooks
5
+ class <%= class_name %>Handler < BaseHandler
6
+ def call
7
+ # Implement dynamic business action logic for <%= file_name %> event here.
8
+ # Access the event details via `event_payload`.
9
+ # Example:
10
+ # Rails.logger.info "Processing <%= file_name %> with payload: #{event_payload.inspect}"
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,75 +1,92 @@
1
- # frozen_string_literal: true
2
-
3
- module Vindi
4
- # =========================================================================
5
- # VINDI WEBHOOK PAYLOAD STRUCTURE EXAMPLE:
6
- # {
7
- # event: {
8
- # id: 123456, # Unique ID for the webhook event (useful for idempotency)
9
- # type: "bill_paid", # Type of event (e.g. subscription_created, bill_paid, charge_rejected)
10
- # created_at: "2026-06-10T15:00:00.000-03:00",
11
- # data: { # Object data (context depends on the event type)
12
- # bill: {
13
- # id: 555,
14
- # amount: "100.00",
15
- # status: "paid",
16
- # customer: { id: 123, email: "john@example.com", code: "app_user_id" },
17
- # charges: [...]
18
- # }
19
- # }
20
- # }
21
- # }
22
- # =========================================================================
23
- class WebhookJob < ActiveJob::Base
24
- queue_as :default
25
-
26
- def perform(payload)
27
- event_id = payload.dig(:event, :id)
28
- event_type = payload.dig(:event, :type)
29
-
30
- # BEST PRACTICE 1: Idempotency Check
31
- # Check if this event_id has already been processed to avoid duplicate actions.
32
- return if already_processed?(event_id)
33
-
34
- process_event(event_type, payload.dig(:event, :data))
35
-
36
- # BEST PRACTICE 2: Record that this event was successfully processed
37
- mark_as_processed!(event_id)
38
- end
39
-
40
- private
41
-
42
- def process_event(event_type, data)
43
- case event_type
44
- when "subscription_created"
45
- handle_subscription_created(data[:subscription])
46
- when "bill_paid"
47
- handle_bill_paid(data[:bill])
48
- else
49
- logger.info "Unhandled Vindi webhook event: #{event_type}"
50
- end
51
- end
52
-
53
- # BEST PRACTICE 3: Safe target state validations (e.g. ignore if already active/paid)
54
- def handle_subscription_created(subscription_data)
55
- return if subscription_data.nil?
56
- # e.g., Find local tenant/user using subscription_data[:customer][:code]
57
- # and activate their account.
58
- end
59
-
60
- def handle_bill_paid(bill_data)
61
- return if bill_data.nil?
62
- # e.g., Find local invoice by bill_data[:id]
63
- # and mark as paid. Check first if it isn't already paid.
64
- end
65
-
66
- def already_processed?(event_id)
67
- # e.g., VindiWebhookEvent.exists?(vindi_event_id: event_id)
68
- false
69
- end
70
-
71
- def mark_as_processed!(event_id)
72
- # e.g., VindiWebhookEvent.create!(vindi_event_id: event_id, processed_at: Time.current)
73
- end
74
- end
75
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Vindi
4
+ # =========================================================================
5
+ # VINDI WEBHOOK PAYLOAD STRUCTURE EXAMPLE:
6
+ # {
7
+ # event: {
8
+ # id: 123456, # Unique ID for the webhook event (useful for idempotency)
9
+ # type: "bill_paid", # Type of event (e.g. subscription_created, bill_paid, charge_rejected)
10
+ # created_at: "2026-06-10T15:00:00.000-03:00",
11
+ # data: { # Object data (context depends on the event type)
12
+ # bill: {
13
+ # id: 555,
14
+ # amount: "100.00",
15
+ # status: "paid",
16
+ # customer: { id: 123, email: "john@example.com", code: "app_user_id" },
17
+ # charges: [...]
18
+ # }
19
+ # }
20
+ # }
21
+ # }
22
+ # =========================================================================
23
+ class WebhookJob < ActiveJob::Base
24
+ queue_as :default
25
+
26
+ def perform(payload)
27
+ event_id = payload.dig(:event, :id)
28
+ event_type = payload.dig(:event, :type)
29
+
30
+ # BEST PRACTICE 1: Idempotency Check
31
+ # Check if this event_id has already been processed to avoid duplicate actions.
32
+ return if already_processed?(event_id)
33
+
34
+ process_event(event_type, payload.dig(:event, :data))
35
+
36
+ # BEST PRACTICE 2: Record that this event was successfully processed
37
+ mark_as_processed!(event_id)
38
+ end
39
+
40
+ private
41
+
42
+ def process_event(event_type, data)
43
+ handler_klass = find_handler_class(event_type)
44
+ if handler_klass
45
+ handler_klass.new(data).call
46
+ else
47
+ fallback_process_event(event_type, data)
48
+ end
49
+ end
50
+
51
+ def find_handler_class(event_type)
52
+ return if event_type.blank?
53
+
54
+ "Vindi::Webhooks::#{event_type.camelize}Handler".safe_constantize
55
+ rescue NameError
56
+ nil
57
+ end
58
+
59
+ def fallback_process_event(event_type, data)
60
+ case event_type
61
+ when "subscription_created"
62
+ handle_subscription_created(data[:subscription])
63
+ when "bill_paid"
64
+ handle_bill_paid(data[:bill])
65
+ else
66
+ logger.info "Unhandled Vindi webhook event: #{event_type}"
67
+ end
68
+ end
69
+
70
+ # BEST PRACTICE 3: Safe target state validations (e.g. ignore if already active/paid)
71
+ def handle_subscription_created(subscription_data)
72
+ return if subscription_data.nil?
73
+ # e.g., Find local tenant/user using subscription_data[:customer][:code]
74
+ # and activate their account.
75
+ end
76
+
77
+ def handle_bill_paid(bill_data)
78
+ return if bill_data.nil?
79
+ # e.g., Find local invoice by bill_data[:id]
80
+ # and mark as paid. Check first if it isn't already paid.
81
+ end
82
+
83
+ def already_processed?(event_id)
84
+ # e.g., VindiWebhookEvent.exists?(vindi_event_id: event_id)
85
+ false
86
+ end
87
+
88
+ def mark_as_processed!(event_id)
89
+ # e.g., VindiWebhookEvent.create!(vindi_event_id: event_id, processed_at: Time.current)
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Vindi
6
+ module Generators
7
+ class WebhookHandlerGenerator < ::Rails::Generators::NamedBase
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ desc "Creates a specific modular webhook handler and a base handler if it does not exist."
11
+
12
+ def create_webhook_handler_files
13
+ template "base_handler.rb", "app/services/vindi/webhooks/base_handler.rb"
14
+ template "webhook_handler.rb", "app/services/vindi/webhooks/#{file_name}_handler.rb"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Vindi
4
4
  module Integrations
5
- VERSION = "0.2.0"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vindi-rails-integrations
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wesley Lima
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-06-11 00:00:00.000000000 Z
11
+ date: 2026-06-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: vindi-rails
@@ -146,10 +146,13 @@ extra_rdoc_files: []
146
146
  files:
147
147
  - README.md
148
148
  - lib/generators/vindi/sync_generator.rb
149
+ - lib/generators/vindi/templates/base_handler.rb
149
150
  - lib/generators/vindi/templates/migration.rb
151
+ - lib/generators/vindi/templates/webhook_handler.rb
150
152
  - lib/generators/vindi/templates/webhook_job.rb
151
153
  - lib/generators/vindi/templates/webhooks_controller.rb
152
154
  - lib/generators/vindi/webhook_generator.rb
155
+ - lib/generators/vindi/webhook_handler_generator.rb
153
156
  - lib/tasks/vindi/tasks.rake
154
157
  - lib/vindi-rails-integrations.rb
155
158
  - lib/vindi/integrations/concerns/synchronizable.rb