vindi-rails 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/CHANGELOG.md +11 -0
- data/CHANGELOG.pt-BR.md +11 -0
- data/README.md +96 -74
- data/README.pt-BR.md +97 -74
- data/WIKI.md +479 -246
- data/WIKI.pt-BR.md +479 -246
- data/lib/vindi/api_operations/create.rb +18 -14
- data/lib/vindi/api_operations/update.rb +18 -14
- data/lib/vindi/resource.rb +36 -31
- data/lib/vindi/version.rb +1 -1
- metadata +2 -2
data/WIKI.md
CHANGED
|
@@ -1,246 +1,479 @@
|
|
|
1
|
-
# Vindi SDK Wiki
|
|
2
|
-
|
|
3
|
-
[Leia em Português (WIKI.pt-BR.md)](./WIKI.pt-BR.md)
|
|
4
|
-
|
|
5
|
-
Welcome to the `vindi-rails` SDK Wiki. This document details all the mapped resources, operations, and advanced usage guidelines.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 1. SDK Design & Architecture
|
|
10
|
-
|
|
11
|
-
The SDK uses `RestClient` internally to communicate with the Vindi API.
|
|
12
|
-
Resources are represented as Ruby objects inheriting from `Vindi::Resource`. Attributes returned from the API are accessible via dot-notation methods on the instances.
|
|
13
|
-
|
|
14
|
-
### Error Handling
|
|
15
|
-
|
|
16
|
-
The SDK raises specific error classes based on HTTP status codes. All errors inherit from `Vindi::Error`:
|
|
17
|
-
|
|
18
|
-
- `Vindi::UnauthorizedError` (401)
|
|
19
|
-
- `Vindi::ForbiddenError` (403)
|
|
20
|
-
- `Vindi::NotFoundError` (404)
|
|
21
|
-
- `Vindi::UnprocessableEntityError` (422)
|
|
22
|
-
- `Vindi::RateLimitError` (429)
|
|
23
|
-
- `Vindi::InternalServerError` (500+)
|
|
24
|
-
- `Vindi::APIError` (Fallback for other HTTP statuses)
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## 2. Resource Mapping & CRUD Capabilities
|
|
29
|
-
|
|
30
|
-
The table below lists all mapped resources and their operations:
|
|
31
|
-
|
|
32
|
-
| Resource Class | Endpoint | CRUD Operations Enabled |
|
|
33
|
-
| :--- | :--- | :--- |
|
|
34
|
-
| `Vindi::Customer` | `customers` | `list`, `create`, `update`, `delete` |
|
|
35
|
-
| `Vindi::PaymentProfile` | `payment_profiles` | `list`, `create`, `delete` |
|
|
36
|
-
| `Vindi::Subscription` | `subscriptions` | `list`, `create`, `update`, `delete` |
|
|
37
|
-
| `Vindi::Charge` | `charges` | `list`, `create`, `update` |
|
|
38
|
-
| `Vindi::Plan` | `plans` | `list`, `create`, `update`, `delete` |
|
|
39
|
-
| `Vindi::Product` | `products` | `list`, `create`, `update`, `delete` |
|
|
40
|
-
| `Vindi::ProductItem` | `product_items` | `list`, `create`, `update`, `delete` |
|
|
41
|
-
| `Vindi::Discount` | `discounts` | `list`, `create`, `delete` |
|
|
42
|
-
| `Vindi::Bill` | `bills` | `list`, `create`, `update`, `delete` |
|
|
43
|
-
| `Vindi::BillItem` | `bill_items` | `list` |
|
|
44
|
-
| `Vindi::Period` | `periods` | `list` |
|
|
45
|
-
| `Vindi::Transaction` | `transactions` | `list`, `create` |
|
|
46
|
-
| `Vindi::Usage` | `usages` | `list`, `create`, `delete` |
|
|
47
|
-
| `Vindi::Invoice` | `invoices` | `list` |
|
|
48
|
-
| `Vindi::Movement` | `movements` | `list` |
|
|
49
|
-
| `Vindi::Message` | `messages` | `list` |
|
|
50
|
-
| `Vindi::ExportBatch` | `export_batches` | `list`, `create` |
|
|
51
|
-
| `Vindi::ImportBatch` | `import_batches` | `list`, `create` |
|
|
52
|
-
| `Vindi::Issue` | `issues` | `list`, `update` |
|
|
53
|
-
| `Vindi::Notification` | `notifications` | `list` |
|
|
54
|
-
| `Vindi::Merchant` | `merchants` | `list` |
|
|
55
|
-
| `Vindi::MerchantUser` | `merchant_users` | `list` |
|
|
56
|
-
| `Vindi::Role` | `roles` | `list` |
|
|
57
|
-
| `Vindi::User` | `users` | `list` |
|
|
58
|
-
| `Vindi::Public` | `public` | None (Static Config) |
|
|
59
|
-
| `Vindi::Affiliate` | `affiliates` | `list` |
|
|
60
|
-
| `Vindi::Partner` | `partner` | `list` |
|
|
61
|
-
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
## 3. Usage Examples
|
|
65
|
-
|
|
66
|
-
### Customers (`Vindi::Customer`)
|
|
67
|
-
|
|
68
|
-
```ruby
|
|
69
|
-
# Create a customer
|
|
70
|
-
customer = Vindi::Customer.create(
|
|
71
|
-
name: "John Doe",
|
|
72
|
-
email: "john@example.com"
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
#
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
#
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
#
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
#
|
|
243
|
-
|
|
244
|
-
batch_type: "
|
|
245
|
-
|
|
246
|
-
|
|
1
|
+
# Vindi SDK Wiki
|
|
2
|
+
|
|
3
|
+
[Leia em Português (WIKI.pt-BR.md)](./WIKI.pt-BR.md)
|
|
4
|
+
|
|
5
|
+
Welcome to the `vindi-rails` SDK Wiki. This document details all the mapped resources, operations, and advanced usage guidelines.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. SDK Design & Architecture
|
|
10
|
+
|
|
11
|
+
The SDK uses `RestClient` internally to communicate with the Vindi API.
|
|
12
|
+
Resources are represented as Ruby objects inheriting from `Vindi::Resource`. Attributes returned from the API are accessible via dot-notation methods on the instances.
|
|
13
|
+
|
|
14
|
+
### Error Handling
|
|
15
|
+
|
|
16
|
+
The SDK raises specific error classes based on HTTP status codes. All errors inherit from `Vindi::Error`:
|
|
17
|
+
|
|
18
|
+
- `Vindi::UnauthorizedError` (401)
|
|
19
|
+
- `Vindi::ForbiddenError` (403)
|
|
20
|
+
- `Vindi::NotFoundError` (404)
|
|
21
|
+
- `Vindi::UnprocessableEntityError` (422)
|
|
22
|
+
- `Vindi::RateLimitError` (429)
|
|
23
|
+
- `Vindi::InternalServerError` (500+)
|
|
24
|
+
- `Vindi::APIError` (Fallback for other HTTP statuses)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 2. Resource Mapping & CRUD Capabilities
|
|
29
|
+
|
|
30
|
+
The table below lists all mapped resources and their operations:
|
|
31
|
+
|
|
32
|
+
| Resource Class | Endpoint | CRUD Operations Enabled |
|
|
33
|
+
| :--- | :--- | :--- |
|
|
34
|
+
| `Vindi::Customer` | `customers` | `list`, `create`, `update`, `delete` |
|
|
35
|
+
| `Vindi::PaymentProfile` | `payment_profiles` | `list`, `create`, `delete` |
|
|
36
|
+
| `Vindi::Subscription` | `subscriptions` | `list`, `create`, `update`, `delete` |
|
|
37
|
+
| `Vindi::Charge` | `charges` | `list`, `create`, `update` |
|
|
38
|
+
| `Vindi::Plan` | `plans` | `list`, `create`, `update`, `delete` |
|
|
39
|
+
| `Vindi::Product` | `products` | `list`, `create`, `update`, `delete` |
|
|
40
|
+
| `Vindi::ProductItem` | `product_items` | `list`, `create`, `update`, `delete` |
|
|
41
|
+
| `Vindi::Discount` | `discounts` | `list`, `create`, `delete` |
|
|
42
|
+
| `Vindi::Bill` | `bills` | `list`, `create`, `update`, `delete` |
|
|
43
|
+
| `Vindi::BillItem` | `bill_items` | `list` |
|
|
44
|
+
| `Vindi::Period` | `periods` | `list` |
|
|
45
|
+
| `Vindi::Transaction` | `transactions` | `list`, `create` |
|
|
46
|
+
| `Vindi::Usage` | `usages` | `list`, `create`, `delete` |
|
|
47
|
+
| `Vindi::Invoice` | `invoices` | `list` |
|
|
48
|
+
| `Vindi::Movement` | `movements` | `list` |
|
|
49
|
+
| `Vindi::Message` | `messages` | `list` |
|
|
50
|
+
| `Vindi::ExportBatch` | `export_batches` | `list`, `create` |
|
|
51
|
+
| `Vindi::ImportBatch` | `import_batches` | `list`, `create` |
|
|
52
|
+
| `Vindi::Issue` | `issues` | `list`, `update` |
|
|
53
|
+
| `Vindi::Notification` | `notifications` | `list` |
|
|
54
|
+
| `Vindi::Merchant` | `merchants` | `list` |
|
|
55
|
+
| `Vindi::MerchantUser` | `merchant_users` | `list` |
|
|
56
|
+
| `Vindi::Role` | `roles` | `list` |
|
|
57
|
+
| `Vindi::User` | `users` | `list` |
|
|
58
|
+
| `Vindi::Public` | `public` | None (Static Config) |
|
|
59
|
+
| `Vindi::Affiliate` | `affiliates` | `list` |
|
|
60
|
+
| `Vindi::Partner` | `partner` | `list` |
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 3. Usage Examples
|
|
65
|
+
|
|
66
|
+
### Customers (`Vindi::Customer`)
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
# Create a customer
|
|
70
|
+
customer = Vindi::Customer.create(
|
|
71
|
+
name: "John Doe",
|
|
72
|
+
email: "john@example.com"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Create a customer with an idempotency key (prevents duplicates)
|
|
76
|
+
customer = Vindi::Customer.create(
|
|
77
|
+
{ name: "John Doe", email: "john@example.com" },
|
|
78
|
+
idempotency_key: "unique-uuid-or-key"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Fetch attribute
|
|
82
|
+
puts customer.id
|
|
83
|
+
puts customer.name
|
|
84
|
+
|
|
85
|
+
# Update customer
|
|
86
|
+
updated = Vindi::Customer.update(customer.id, email: "john.new@example.com")
|
|
87
|
+
|
|
88
|
+
# Delete customer
|
|
89
|
+
Vindi::Customer.delete(customer.id)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Payment Profiles (`Vindi::PaymentProfile`)
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
# Create a card payment profile
|
|
96
|
+
profile = Vindi::PaymentProfile.create(
|
|
97
|
+
customer_id: 12345,
|
|
98
|
+
payment_company_code: "visa",
|
|
99
|
+
holder_name: "JOHN DOE",
|
|
100
|
+
card_number: "4111111111111111",
|
|
101
|
+
card_expiration_date: "12/2030",
|
|
102
|
+
card_cvv: "123"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Delete payment profile
|
|
106
|
+
Vindi::PaymentProfile.delete(profile.id)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Subscriptions (`Vindi::Subscription`)
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
# Create subscription
|
|
113
|
+
subscription = Vindi::Subscription.create(
|
|
114
|
+
customer_id: customer.id,
|
|
115
|
+
plan_id: plan.id,
|
|
116
|
+
payment_method_code: "credit_card"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Cancel/Delete subscription
|
|
120
|
+
Vindi::Subscription.delete(subscription.id)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Charges (`Vindi::Charge`)
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
# List charges
|
|
127
|
+
charges = Vindi::Charge.list(status: "pending")
|
|
128
|
+
|
|
129
|
+
# Charge details
|
|
130
|
+
puts charges.first.amount
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Plans (`Vindi::Plan`)
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
# Create a plan
|
|
137
|
+
plan = Vindi::Plan.create(
|
|
138
|
+
name: "Premium Gold Plan",
|
|
139
|
+
code: "gold_premium",
|
|
140
|
+
interval: "months",
|
|
141
|
+
interval_count: 1
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# List plans
|
|
145
|
+
plans = Vindi::Plan.list
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Products & Product Items (`Vindi::Product` & `Vindi::ProductItem`)
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
# Create a product
|
|
152
|
+
product = Vindi::Product.create(
|
|
153
|
+
name: "Hosting Service",
|
|
154
|
+
code: "hosting"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Associate a product to a plan/subscription
|
|
158
|
+
product_item = Vindi::ProductItem.create(
|
|
159
|
+
product_id: product.id,
|
|
160
|
+
plan_id: plan.id
|
|
161
|
+
)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Discounts (`Vindi::Discount`)
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
# Create/apply a discount
|
|
168
|
+
discount = Vindi::Discount.create(
|
|
169
|
+
amount: 15.00,
|
|
170
|
+
discount_type: "percentage",
|
|
171
|
+
percentage: 10
|
|
172
|
+
)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Bills & Bill Items (`Vindi::Bill` & `Vindi::BillItem`)
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
# Create a bill manually
|
|
179
|
+
bill = Vindi::Bill.create(
|
|
180
|
+
customer_id: customer.id,
|
|
181
|
+
payment_method_code: "credit_card",
|
|
182
|
+
bill_items: [
|
|
183
|
+
{ product_id: product.id, amount: 99.90 }
|
|
184
|
+
]
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# List bill items
|
|
188
|
+
items = Vindi::BillItem.list(bill_id: bill.id)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Periods (`Vindi::Period`)
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
# List subscription periods
|
|
195
|
+
periods = Vindi::Period.list(subscription_id: subscription.id)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Transactions (`Vindi::Transaction`)
|
|
199
|
+
|
|
200
|
+
```ruby
|
|
201
|
+
# Create a manual transaction/charge capture
|
|
202
|
+
transaction = Vindi::Transaction.create(
|
|
203
|
+
charge_id: charge.id,
|
|
204
|
+
amount: 99.90
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# List transactions
|
|
208
|
+
transactions = Vindi::Transaction.list
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Usages (`Vindi::Usage`)
|
|
212
|
+
|
|
213
|
+
```ruby
|
|
214
|
+
# Report a metered usage for a subscription
|
|
215
|
+
usage = Vindi::Usage.create(
|
|
216
|
+
subscription_id: subscription.id,
|
|
217
|
+
quantity: 50,
|
|
218
|
+
description: "API Calls consumed"
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# List usages
|
|
222
|
+
usages = Vindi::Usage.list(subscription_id: subscription.id)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Invoices (`Vindi::Invoice`)
|
|
226
|
+
|
|
227
|
+
```ruby
|
|
228
|
+
# List invoices
|
|
229
|
+
invoices = Vindi::Invoice.list(status: "issued")
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Issues (`Vindi::Issue`)
|
|
233
|
+
|
|
234
|
+
```ruby
|
|
235
|
+
# Update an issue status (e.g. resolve a billing conflict)
|
|
236
|
+
issue = Vindi::Issue.update(issue_id, status: "resolved")
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Import/Export Batches (`Vindi::ImportBatch` & `Vindi::ExportBatch`)
|
|
240
|
+
|
|
241
|
+
```ruby
|
|
242
|
+
# Start a new customer/subscription import batch
|
|
243
|
+
import_batch = Vindi::ImportBatch.create(
|
|
244
|
+
batch_type: "customer",
|
|
245
|
+
file_url: "https://example.com/import.csv"
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# Request a data export batch
|
|
249
|
+
export_batch = Vindi::ExportBatch.create(
|
|
250
|
+
batch_type: "bill"
|
|
251
|
+
)
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## 4. Extensible Companion Gems
|
|
257
|
+
|
|
258
|
+
To keep the core SDK lightweight and dependency-free, Rails-specific features are distributed via companion gems.
|
|
259
|
+
|
|
260
|
+
### 4.1 Backend Integrations ([`vindi-rails-integrations`](https://github.com/wesleyskap/vindi-rails-integrations))
|
|
261
|
+
|
|
262
|
+
Provides automatic ActiveRecord model synchronization, webhook controller endpoints, background processing jobs, and administration Rake tasks.
|
|
263
|
+
|
|
264
|
+
#### Installation
|
|
265
|
+
Add it to your Gemfile:
|
|
266
|
+
```ruby
|
|
267
|
+
gem 'vindi-rails-integrations'
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
#### Webhook Setup
|
|
271
|
+
Generate the webhook endpoint controller and processing job in your host application:
|
|
272
|
+
```bash
|
|
273
|
+
bundle exec rails generate vindi:webhook
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
##### 1. Webhooks Controller (`app/controllers/vindi/webhooks_controller.rb`)
|
|
277
|
+
Validates incoming Vindi HTTP POST notifications by checking a secure token inside the query parameters.
|
|
278
|
+
- **Webhook Endpoint**: `POST /vindi/webhooks?token=YOUR_SECURE_TOKEN`
|
|
279
|
+
- **Controller Response**: Returns `{ "status": "received" }` with HTTP status `200 OK` on success, or `{ "error": "Unauthorized access token" }` (`401 Unauthorized`) / `{ "error": "Invalid payload" }` (`400 Bad Request`).
|
|
280
|
+
|
|
281
|
+
##### 2. Asynchronous Webhook Job (`app/jobs/vindi/webhook_job.rb`)
|
|
282
|
+
Handles processing events in the background. It includes built-in safeguards like idempotency checking.
|
|
283
|
+
|
|
284
|
+
##### 3. Modular Webhook Handlers
|
|
285
|
+
For cleaner architectures, separate processing of each event type into dedicated service files:
|
|
286
|
+
```bash
|
|
287
|
+
bundle exec rails generate vindi:webhook_handler subscription_canceled
|
|
288
|
+
```
|
|
289
|
+
This generates `app/services/vindi/webhooks/base_handler.rb` (base class) and `app/services/vindi/webhooks/subscription_canceled_handler.rb`. The generated `WebhookJob` will automatically discover and dispatch to these modular classes dynamically.
|
|
290
|
+
|
|
291
|
+
###### Example Vindi Webhook Payload (Event)
|
|
292
|
+
```json
|
|
293
|
+
{
|
|
294
|
+
"event": {
|
|
295
|
+
"id": 1928374,
|
|
296
|
+
"type": "bill_paid",
|
|
297
|
+
"created_at": "2026-06-10T15:00:00.000-03:00",
|
|
298
|
+
"data": {
|
|
299
|
+
"bill": {
|
|
300
|
+
"id": 887766,
|
|
301
|
+
"amount": "150.00",
|
|
302
|
+
"status": "paid",
|
|
303
|
+
"customer": {
|
|
304
|
+
"id": 112233,
|
|
305
|
+
"name": "Jane Doe",
|
|
306
|
+
"email": "jane.doe@example.com",
|
|
307
|
+
"code": "user_42"
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
#### ActiveRecord Synchronization
|
|
316
|
+
Map any ActiveRecord model (e.g., `User`, `Account`) to synchronize automatically with Vindi Customers.
|
|
317
|
+
|
|
318
|
+
##### 1. Generator Setup
|
|
319
|
+
Run the sync generator for your model:
|
|
320
|
+
```bash
|
|
321
|
+
bundle exec rails generate vindi:sync User
|
|
322
|
+
```
|
|
323
|
+
This generates a database migration to add `vindi_customer_id` (String) to your users table and configures the `Vindi::Synchronizable` concern.
|
|
324
|
+
|
|
325
|
+
##### 2. Usage & Override Customization
|
|
326
|
+
Include `Vindi::Synchronizable` in your model and customize the attributes payload sent to Vindi:
|
|
327
|
+
```ruby
|
|
328
|
+
class User < ApplicationRecord
|
|
329
|
+
include Vindi::Synchronizable
|
|
330
|
+
|
|
331
|
+
# Optional: Customize the attributes synced with Vindi
|
|
332
|
+
def vindi_customer_attributes
|
|
333
|
+
{
|
|
334
|
+
name: "#{first_name} #{last_name}",
|
|
335
|
+
email: email,
|
|
336
|
+
registry_code: cpf_or_cnpj, # If applicable
|
|
337
|
+
code: "user_#{id}"
|
|
338
|
+
}
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
When a user record is committed on create or update, it triggers:
|
|
344
|
+
- **On Create**: Calls `Vindi::Customer.create` and populates `vindi_customer_id` automatically in your database.
|
|
345
|
+
- **On Update**: Automatically checks if `name` or `email` changed, and triggers `Vindi::Customer.update` to keep records in sync.
|
|
346
|
+
|
|
347
|
+
##### 3. Resilient Transactional Outbox Sync (Optional)
|
|
348
|
+
To prevent network requests inside your main database transactions and ensure ultimate reliability:
|
|
349
|
+
|
|
350
|
+
1. Generate outbox migration:
|
|
351
|
+
```bash
|
|
352
|
+
bundle exec rails generate vindi:outbox
|
|
353
|
+
bundle exec rails db:migrate
|
|
354
|
+
```
|
|
355
|
+
2. Enable it in your config initializer:
|
|
356
|
+
```ruby
|
|
357
|
+
Vindi.configure do |config|
|
|
358
|
+
config.use_outbox = true
|
|
359
|
+
end
|
|
360
|
+
```
|
|
361
|
+
3. When a record is created or updated, the synchronization is written to `vindi_pending_syncs` in-transaction, and `Vindi::ProcessPendingSyncsJob` is enqueued after the commit to process it in background.
|
|
362
|
+
|
|
363
|
+
#### Rake Tasks
|
|
364
|
+
The gem includes task commands for verification:
|
|
365
|
+
|
|
366
|
+
##### 1. Auditing (`vindi:audit`)
|
|
367
|
+
Reconcile your local ActiveRecord model records against Vindi customers API:
|
|
368
|
+
```bash
|
|
369
|
+
bundle exec rake vindi:audit model=User
|
|
370
|
+
```
|
|
371
|
+
**Example Audit Output Log:**
|
|
372
|
+
```text
|
|
373
|
+
Analyzing User database...
|
|
374
|
+
[Audit] Checking User ID: 42 (Vindi ID: 112233) - Match found.
|
|
375
|
+
[Audit] Checking User ID: 43 (Vindi ID: nil) - Missing in Vindi!
|
|
376
|
+
[Audit Warning] User ID 43 created in Vindi with customer ID 112234.
|
|
377
|
+
Reconciliation complete. 1 missing records synchronized.
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
##### 2. Local Webhook Test Simulator (`vindi:test_webhook`)
|
|
381
|
+
Simulate webhook event POST requests to your Rails environment locally:
|
|
382
|
+
```bash
|
|
383
|
+
bundle exec rake vindi:test_webhook event=bill_paid url=http://localhost:3000/vindi/webhooks token=YOUR_SECURE_TOKEN
|
|
384
|
+
```
|
|
385
|
+
**Example Request Sent:**
|
|
386
|
+
```text
|
|
387
|
+
Sending POST to http://localhost:3000/vindi/webhooks?token=YOUR_SECURE_TOKEN...
|
|
388
|
+
Payload: {"event":{"id":9999,"type":"bill_paid","data":{...}}}
|
|
389
|
+
Response Code: 200 OK
|
|
390
|
+
Response Body: {"status":"received"}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
##### 3. Diagnostics and Connectivity Check (`vindi:status`)
|
|
394
|
+
Verify your configured API environments, credentials safely (masked), and run connectivity health checks:
|
|
395
|
+
```bash
|
|
396
|
+
bundle exec rake vindi:status
|
|
397
|
+
```
|
|
398
|
+
**Example Status Report:**
|
|
399
|
+
```text
|
|
400
|
+
=== Vindi Integration Status ===
|
|
401
|
+
Environment: Sandbox
|
|
402
|
+
API URL: https://sandbox-gp.vindi.com.br/api/v1
|
|
403
|
+
API Key: *****2345
|
|
404
|
+
Webhook: *****_999
|
|
405
|
+
--------------------------------
|
|
406
|
+
Connectivity: SUCCESS
|
|
407
|
+
================================
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
### 4.2 Frontend Engines ([`vindi-rails-engines`](https://github.com/wesleyskap/vindi-rails-engines))
|
|
413
|
+
|
|
414
|
+
Mountable UI resources providing PCI-compliant client side tokenization components.
|
|
415
|
+
|
|
416
|
+
#### Installation
|
|
417
|
+
Add it to your Gemfile:
|
|
418
|
+
```ruby
|
|
419
|
+
gem 'vindi-rails-engines'
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
#### Checkout UI Setup
|
|
423
|
+
Initialize the tokenization view templates and stimulus controllers:
|
|
424
|
+
```bash
|
|
425
|
+
bundle exec rails generate vindi:checkout
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
##### 1. Stimulus JS Controller (`app/javascript/controllers/vindi_checkout_controller.js`)
|
|
429
|
+
Intercepts the form submission, serializes card inputs, interacts with Vindi's public tokenization client, and sets the returned token into a hidden field:
|
|
430
|
+
```javascript
|
|
431
|
+
// Connects to data-controller="vindi-checkout"
|
|
432
|
+
import { Controller } from "@hotwired/stimulus"
|
|
433
|
+
|
|
434
|
+
export default class extends Controller {
|
|
435
|
+
static targets = [ "publicKey", "holderName", "cardNumber", "expiry", "cvv", "tokenInput" ]
|
|
436
|
+
|
|
437
|
+
tokenizeCard(event) {
|
|
438
|
+
event.preventDefault()
|
|
439
|
+
const vindi = new Vindi(this.publicKeyTarget.value)
|
|
440
|
+
|
|
441
|
+
vindi.createToken({
|
|
442
|
+
holder_name: this.holderNameTarget.value,
|
|
443
|
+
card_number: this.cardNumberTarget.value.replace(/\s+/g, ''),
|
|
444
|
+
card_expiration: this.expiryTarget.value,
|
|
445
|
+
card_cvv: this.cvvTarget.value
|
|
446
|
+
}).then((response) => {
|
|
447
|
+
// response: { token: "tok_abc123XYZ", created_at: "2026-06-10..." }
|
|
448
|
+
this.tokenInputTarget.value = response.token
|
|
449
|
+
this.element.submit()
|
|
450
|
+
}).catch((error) => {
|
|
451
|
+
alert("Tokenization Error: " + error.message)
|
|
452
|
+
})
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
##### 2. Vindi Public Key Response Example
|
|
458
|
+
When calling `vindi.createToken(...)` with card credentials, the Javascript SDK returns the payment profile token:
|
|
459
|
+
```json
|
|
460
|
+
{
|
|
461
|
+
"token": "tok_3278918239abc",
|
|
462
|
+
"created_at": "2026-06-10T16:50:00.000-03:00"
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Use the generated `payment_profile_token` in your backend controllers to safely set up new subscriptions or charge invoices without storing credit card details locally:
|
|
467
|
+
```ruby
|
|
468
|
+
# Example Controller endpoint processing checkout token
|
|
469
|
+
def charge
|
|
470
|
+
Vindi::Charge.create(
|
|
471
|
+
payment_method_code: "credit_card",
|
|
472
|
+
payment_profile: {
|
|
473
|
+
token: params[:payment_profile_token]
|
|
474
|
+
},
|
|
475
|
+
amount: "150.00",
|
|
476
|
+
customer_id: current_user.vindi_customer_id
|
|
477
|
+
)
|
|
478
|
+
end
|
|
479
|
+
```
|