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 +4 -4
- data/README.md +59 -48
- data/lib/generators/vindi/templates/base_handler.rb +17 -0
- data/lib/generators/vindi/templates/webhook_handler.rb +14 -0
- data/lib/generators/vindi/templates/webhook_job.rb +92 -75
- data/lib/generators/vindi/webhook_handler_generator.rb +18 -0
- data/lib/vindi/integrations/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ac62896e691574d5b96e6ed7b00e2456222db8b7d575393199e1e5d2f982aaaf
|
|
4
|
+
data.tar.gz: 95876321b904170ac20f12163240fa140fecf3747f3383e725220b16fdf8b7e9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
bundle exec rails generate vindi:
|
|
35
|
-
```
|
|
36
|
-
This generates
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
```
|
|
47
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
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.
|
|
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
|
+
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
|