anvil-ruby 0.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 +7 -0
- data/AGENTS.md +123 -0
- data/API_COVERAGE.md +294 -0
- data/CHANGELOG.md +34 -0
- data/CLAUDE_README.md +98 -0
- data/GITHUB_ACTIONS_QUICKREF.md +174 -0
- data/Gemfile.lock +112 -0
- data/Gemfile.minimal +9 -0
- data/LICENSE +21 -0
- data/PROJECT_CONTEXT.md +196 -0
- data/README.md +445 -0
- data/anvil-ruby.gemspec +66 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/create_signature_direct.rb +142 -0
- data/create_signature_packet.rb +232 -0
- data/debug_env.rb +28 -0
- data/examples/create_signature.rb +194 -0
- data/examples/fill_pdf.rb +89 -0
- data/examples/generate_pdf.rb +347 -0
- data/examples/verify_webhook.rb +281 -0
- data/lib/anvil/client.rb +216 -0
- data/lib/anvil/configuration.rb +87 -0
- data/lib/anvil/env_loader.rb +30 -0
- data/lib/anvil/errors.rb +95 -0
- data/lib/anvil/rate_limiter.rb +66 -0
- data/lib/anvil/resources/base.rb +100 -0
- data/lib/anvil/resources/pdf.rb +171 -0
- data/lib/anvil/resources/signature.rb +517 -0
- data/lib/anvil/resources/webform.rb +154 -0
- data/lib/anvil/resources/webhook.rb +201 -0
- data/lib/anvil/resources/workflow.rb +169 -0
- data/lib/anvil/response.rb +98 -0
- data/lib/anvil/version.rb +5 -0
- data/lib/anvil.rb +88 -0
- data/quickstart_signature.rb +220 -0
- data/test_api_connection.rb +143 -0
- data/test_etch_signature.rb +230 -0
- data/test_gem.rb +72 -0
- data/test_signature.rb +281 -0
- data/test_signature_with_template.rb +112 -0
- metadata +247 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Add lib to the load path if running directly
|
|
5
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
|
6
|
+
|
|
7
|
+
require 'anvil'
|
|
8
|
+
require 'anvil/env_loader'
|
|
9
|
+
|
|
10
|
+
# Load .env file if it exists
|
|
11
|
+
Anvil::EnvLoader.load(File.expand_path('../.env', __dir__))
|
|
12
|
+
|
|
13
|
+
# Example: Generate PDFs from HTML or Markdown
|
|
14
|
+
#
|
|
15
|
+
# This example shows how to generate PDFs from HTML/CSS or Markdown content
|
|
16
|
+
|
|
17
|
+
# Configure Anvil (will use ANVIL_API_KEY from .env)
|
|
18
|
+
Anvil.configure do |config|
|
|
19
|
+
config.api_key = ENV.fetch('ANVIL_API_KEY', nil)
|
|
20
|
+
config.environment = :development
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Example 1: Generate PDF from HTML
|
|
24
|
+
def generate_html_invoice
|
|
25
|
+
puts "\n๐ Generating invoice from HTML..."
|
|
26
|
+
|
|
27
|
+
html = <<~HTML
|
|
28
|
+
<!DOCTYPE html>
|
|
29
|
+
<html>
|
|
30
|
+
<head>
|
|
31
|
+
<title>Invoice</title>
|
|
32
|
+
</head>
|
|
33
|
+
<body>
|
|
34
|
+
<div class="invoice-header">
|
|
35
|
+
<h1>INVOICE</h1>
|
|
36
|
+
<div class="invoice-number">#INV-2024-001</div>
|
|
37
|
+
<div class="invoice-date">Date: #{Date.today.strftime('%B %d, %Y')}</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div class="company-info">
|
|
41
|
+
<h2>Acme Corporation</h2>
|
|
42
|
+
<p>123 Business Ave<br>San Francisco, CA 94102<br>Phone: (555) 123-4567</p>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div class="bill-to">
|
|
46
|
+
<h3>Bill To:</h3>
|
|
47
|
+
<p>
|
|
48
|
+
John Doe<br>
|
|
49
|
+
XYZ Company<br>
|
|
50
|
+
456 Client Street<br>
|
|
51
|
+
New York, NY 10001
|
|
52
|
+
</p>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<table class="invoice-items">
|
|
56
|
+
<thead>
|
|
57
|
+
<tr>
|
|
58
|
+
<th>Description</th>
|
|
59
|
+
<th>Quantity</th>
|
|
60
|
+
<th>Unit Price</th>
|
|
61
|
+
<th>Total</th>
|
|
62
|
+
</tr>
|
|
63
|
+
</thead>
|
|
64
|
+
<tbody>
|
|
65
|
+
<tr>
|
|
66
|
+
<td>Consulting Services</td>
|
|
67
|
+
<td>40</td>
|
|
68
|
+
<td>$150.00</td>
|
|
69
|
+
<td>$6,000.00</td>
|
|
70
|
+
</tr>
|
|
71
|
+
<tr>
|
|
72
|
+
<td>Software License</td>
|
|
73
|
+
<td>1</td>
|
|
74
|
+
<td>$2,500.00</td>
|
|
75
|
+
<td>$2,500.00</td>
|
|
76
|
+
</tr>
|
|
77
|
+
</tbody>
|
|
78
|
+
<tfoot>
|
|
79
|
+
<tr>
|
|
80
|
+
<td colspan="3">Subtotal</td>
|
|
81
|
+
<td>$8,500.00</td>
|
|
82
|
+
</tr>
|
|
83
|
+
<tr>
|
|
84
|
+
<td colspan="3">Tax (10%)</td>
|
|
85
|
+
<td>$850.00</td>
|
|
86
|
+
</tr>
|
|
87
|
+
<tr class="total">
|
|
88
|
+
<td colspan="3"><strong>Total</strong></td>
|
|
89
|
+
<td><strong>$9,350.00</strong></td>
|
|
90
|
+
</tr>
|
|
91
|
+
</tfoot>
|
|
92
|
+
</table>
|
|
93
|
+
|
|
94
|
+
<div class="footer">
|
|
95
|
+
<p>Payment due within 30 days. Thank you for your business!</p>
|
|
96
|
+
</div>
|
|
97
|
+
</body>
|
|
98
|
+
</html>
|
|
99
|
+
HTML
|
|
100
|
+
|
|
101
|
+
css = <<~CSS
|
|
102
|
+
body {
|
|
103
|
+
font-family: 'Helvetica Neue', Arial, sans-serif;
|
|
104
|
+
color: #333;
|
|
105
|
+
line-height: 1.6;
|
|
106
|
+
padding: 20px;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.invoice-header {
|
|
110
|
+
border-bottom: 2px solid #007bff;
|
|
111
|
+
padding-bottom: 20px;
|
|
112
|
+
margin-bottom: 30px;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
h1 {
|
|
116
|
+
color: #007bff;
|
|
117
|
+
margin: 0;
|
|
118
|
+
font-size: 36px;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.invoice-number {
|
|
122
|
+
font-size: 18px;
|
|
123
|
+
color: #666;
|
|
124
|
+
margin-top: 10px;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.invoice-date {
|
|
128
|
+
font-size: 14px;
|
|
129
|
+
color: #666;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.company-info, .bill-to {
|
|
133
|
+
margin-bottom: 30px;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
h2 {
|
|
137
|
+
color: #333;
|
|
138
|
+
font-size: 24px;
|
|
139
|
+
margin-bottom: 10px;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
h3 {
|
|
143
|
+
color: #555;
|
|
144
|
+
font-size: 18px;
|
|
145
|
+
margin-bottom: 10px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
table {
|
|
149
|
+
width: 100%;
|
|
150
|
+
border-collapse: collapse;
|
|
151
|
+
margin: 30px 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
th {
|
|
155
|
+
background-color: #007bff;
|
|
156
|
+
color: white;
|
|
157
|
+
padding: 12px;
|
|
158
|
+
text-align: left;
|
|
159
|
+
font-weight: bold;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
td {
|
|
163
|
+
padding: 12px;
|
|
164
|
+
border-bottom: 1px solid #ddd;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
tfoot td {
|
|
168
|
+
font-weight: bold;
|
|
169
|
+
border-top: 2px solid #007bff;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.total td {
|
|
173
|
+
font-size: 18px;
|
|
174
|
+
color: #007bff;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.footer {
|
|
178
|
+
margin-top: 40px;
|
|
179
|
+
padding-top: 20px;
|
|
180
|
+
border-top: 1px solid #ddd;
|
|
181
|
+
text-align: center;
|
|
182
|
+
color: #666;
|
|
183
|
+
font-size: 14px;
|
|
184
|
+
}
|
|
185
|
+
CSS
|
|
186
|
+
|
|
187
|
+
pdf = Anvil::PDF.generate_from_html(
|
|
188
|
+
html: html,
|
|
189
|
+
css: css,
|
|
190
|
+
title: 'Invoice #INV-2024-001'
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
filename = "invoice_#{Time.now.to_i}.pdf"
|
|
194
|
+
pdf.save_as(filename)
|
|
195
|
+
|
|
196
|
+
puts 'โ
Invoice PDF generated!'
|
|
197
|
+
puts "๐ Saved as: #{filename}"
|
|
198
|
+
puts "๐ Size: #{pdf.size_human}"
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Example 2: Generate PDF from Markdown
|
|
202
|
+
def generate_markdown_report
|
|
203
|
+
puts "\n๐ Generating report from Markdown..."
|
|
204
|
+
|
|
205
|
+
markdown_content = [
|
|
206
|
+
{
|
|
207
|
+
heading: 'Annual Report 2024',
|
|
208
|
+
content: <<~MD
|
|
209
|
+
## Executive Summary
|
|
210
|
+
|
|
211
|
+
This annual report provides a comprehensive overview of our company's performance
|
|
212
|
+
and achievements during the fiscal year 2024.
|
|
213
|
+
|
|
214
|
+
### Key Highlights
|
|
215
|
+
|
|
216
|
+
- Revenue growth of **25%** year-over-year
|
|
217
|
+
- Expanded operations to **3 new markets**
|
|
218
|
+
- Launched **5 innovative products**
|
|
219
|
+
- Increased customer satisfaction to **95%**
|
|
220
|
+
MD
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
heading: 'Financial Performance',
|
|
224
|
+
content: <<~MD
|
|
225
|
+
### Revenue Breakdown
|
|
226
|
+
|
|
227
|
+
| Quarter | Revenue | Growth |
|
|
228
|
+
|---------|------------|--------|
|
|
229
|
+
| Q1 2024 | $2.5M | +20% |
|
|
230
|
+
| Q2 2024 | $2.8M | +22% |
|
|
231
|
+
| Q3 2024 | $3.2M | +28% |
|
|
232
|
+
| Q4 2024 | $3.5M | +30% |
|
|
233
|
+
| **Total** | **$12M** | **+25%** |
|
|
234
|
+
|
|
235
|
+
### Operating Expenses
|
|
236
|
+
|
|
237
|
+
Our operating expenses remained well-controlled throughout the year:
|
|
238
|
+
|
|
239
|
+
- Personnel: $4.2M
|
|
240
|
+
- Marketing: $1.8M
|
|
241
|
+
- R&D: $2.1M
|
|
242
|
+
- Operations: $1.4M
|
|
243
|
+
MD
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
heading: 'Future Outlook',
|
|
247
|
+
content: <<~MD
|
|
248
|
+
## Strategic Initiatives for 2025
|
|
249
|
+
|
|
250
|
+
1. **Digital Transformation**
|
|
251
|
+
- Implement AI-driven analytics
|
|
252
|
+
- Upgrade customer portal
|
|
253
|
+
- Automate key processes
|
|
254
|
+
|
|
255
|
+
2. **Market Expansion**
|
|
256
|
+
- Enter European markets
|
|
257
|
+
- Strengthen presence in Asia
|
|
258
|
+
- Launch e-commerce platform
|
|
259
|
+
|
|
260
|
+
3. **Product Innovation**
|
|
261
|
+
- Release next-gen product line
|
|
262
|
+
- Enhance mobile applications
|
|
263
|
+
- Develop SaaS offerings
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
*This report was prepared by the Executive Team*
|
|
268
|
+
*Date: #{Date.today.strftime('%B %d, %Y')}*
|
|
269
|
+
MD
|
|
270
|
+
}
|
|
271
|
+
]
|
|
272
|
+
|
|
273
|
+
pdf = Anvil::PDF.generate(
|
|
274
|
+
type: :markdown,
|
|
275
|
+
data: markdown_content,
|
|
276
|
+
title: 'Annual Report 2024',
|
|
277
|
+
page: {
|
|
278
|
+
margin_top: '2in',
|
|
279
|
+
margin_bottom: '1in',
|
|
280
|
+
margin_left: '1in',
|
|
281
|
+
margin_right: '1in'
|
|
282
|
+
}
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
filename = "report_#{Time.now.to_i}.pdf"
|
|
286
|
+
pdf.save_as(filename)
|
|
287
|
+
|
|
288
|
+
puts 'โ
Report PDF generated!'
|
|
289
|
+
puts "๐ Saved as: #{filename}"
|
|
290
|
+
puts "๐ Size: #{pdf.size_human}"
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Example 3: Simple markdown generation
|
|
294
|
+
def generate_simple_document
|
|
295
|
+
puts "\n๐ Generating simple document..."
|
|
296
|
+
|
|
297
|
+
pdf = Anvil::PDF.generate_from_markdown(
|
|
298
|
+
<<~MD
|
|
299
|
+
# Welcome to Anvil
|
|
300
|
+
|
|
301
|
+
This is a simple example of generating a PDF from markdown.
|
|
302
|
+
|
|
303
|
+
## Features
|
|
304
|
+
|
|
305
|
+
- Easy to use API
|
|
306
|
+
- Multiple format support
|
|
307
|
+
- Fast generation
|
|
308
|
+
- Professional output
|
|
309
|
+
|
|
310
|
+
## Code Example
|
|
311
|
+
|
|
312
|
+
```ruby
|
|
313
|
+
pdf = Anvil::PDF.generate_from_markdown(content)
|
|
314
|
+
pdf.save_as('output.pdf')
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Thank you for using Anvil!
|
|
318
|
+
MD
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
filename = "simple_#{Time.now.to_i}.pdf"
|
|
322
|
+
pdf.save_as(filename)
|
|
323
|
+
|
|
324
|
+
puts 'โ
Simple PDF generated!'
|
|
325
|
+
puts "๐ Saved as: #{filename}"
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# Run examples
|
|
329
|
+
begin
|
|
330
|
+
puts '=' * 50
|
|
331
|
+
puts 'Anvil PDF Generation Examples'
|
|
332
|
+
puts '=' * 50
|
|
333
|
+
|
|
334
|
+
generate_html_invoice
|
|
335
|
+
generate_markdown_report
|
|
336
|
+
generate_simple_document
|
|
337
|
+
|
|
338
|
+
puts "\nโ
All examples completed successfully!"
|
|
339
|
+
rescue Anvil::AuthenticationError => e
|
|
340
|
+
puts "โ Authentication failed: #{e.message}"
|
|
341
|
+
puts 'Please set your ANVIL_API_KEY environment variable'
|
|
342
|
+
rescue Anvil::Error => e
|
|
343
|
+
puts "โ Anvil error: #{e.message}"
|
|
344
|
+
rescue StandardError => e
|
|
345
|
+
puts "โ Unexpected error: #{e.message}"
|
|
346
|
+
puts e.backtrace.first(5)
|
|
347
|
+
end
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
require 'anvil'
|
|
6
|
+
require 'webrick'
|
|
7
|
+
require 'json'
|
|
8
|
+
|
|
9
|
+
# Example: Webhook verification and handling
|
|
10
|
+
#
|
|
11
|
+
# This example shows how to:
|
|
12
|
+
# 1. Set up a webhook endpoint
|
|
13
|
+
# 2. Verify webhook authenticity
|
|
14
|
+
# 3. Handle different webhook events
|
|
15
|
+
# 4. Process webhook data
|
|
16
|
+
|
|
17
|
+
# Configure Anvil with webhook token
|
|
18
|
+
Anvil.configure do |config|
|
|
19
|
+
config.api_key = ENV.fetch('ANVIL_API_KEY', nil)
|
|
20
|
+
config.webhook_token = ENV['ANVIL_WEBHOOK_TOKEN'] || 'your_webhook_token_here'
|
|
21
|
+
config.environment = :development
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Example webhook handler class
|
|
25
|
+
class WebhookHandler
|
|
26
|
+
def handle(webhook)
|
|
27
|
+
puts "\n๐จ Received webhook:"
|
|
28
|
+
puts " Action: #{webhook.action}"
|
|
29
|
+
puts " Timestamp: #{webhook.timestamp}"
|
|
30
|
+
|
|
31
|
+
# Handle different webhook types
|
|
32
|
+
case webhook.action
|
|
33
|
+
when 'signerComplete'
|
|
34
|
+
handle_signer_complete(webhook)
|
|
35
|
+
when 'signerUpdateStatus'
|
|
36
|
+
handle_signer_status_update(webhook)
|
|
37
|
+
when 'etchPacketComplete'
|
|
38
|
+
handle_packet_complete(webhook)
|
|
39
|
+
when 'weldCreate'
|
|
40
|
+
handle_workflow_created(webhook)
|
|
41
|
+
when 'weldComplete'
|
|
42
|
+
handle_workflow_complete(webhook)
|
|
43
|
+
when 'forgeComplete'
|
|
44
|
+
handle_webform_complete(webhook)
|
|
45
|
+
when 'documentGroupCreate'
|
|
46
|
+
handle_document_group_created(webhook)
|
|
47
|
+
when 'webhookTest'
|
|
48
|
+
handle_test_webhook(webhook)
|
|
49
|
+
else
|
|
50
|
+
puts " โ ๏ธ Unknown webhook action: #{webhook.action}"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def handle_signer_complete(webhook)
|
|
57
|
+
puts 'โ
Signer completed!'
|
|
58
|
+
puts " Signer: #{webhook.signer_name} (#{webhook.signer_email})"
|
|
59
|
+
puts " Signer ID: #{webhook.signer_eid}"
|
|
60
|
+
puts " Packet ID: #{webhook.packet_eid}"
|
|
61
|
+
|
|
62
|
+
# In a real app, you might:
|
|
63
|
+
# - Update database records
|
|
64
|
+
# - Send notification emails
|
|
65
|
+
# - Trigger next workflow step
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def handle_signer_status_update(webhook)
|
|
69
|
+
puts '๐ Signer status updated'
|
|
70
|
+
puts " Signer: #{webhook.signer_name}"
|
|
71
|
+
puts " New status: #{webhook.signer_status}"
|
|
72
|
+
|
|
73
|
+
case webhook.signer_status
|
|
74
|
+
when 'viewed'
|
|
75
|
+
puts ' ๐ Signer has viewed the document'
|
|
76
|
+
when 'signed'
|
|
77
|
+
puts ' โ๏ธ Signer has signed'
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def handle_packet_complete(webhook)
|
|
82
|
+
puts '๐ Signature packet complete!'
|
|
83
|
+
puts " Packet ID: #{webhook.packet_eid}"
|
|
84
|
+
|
|
85
|
+
# All signatures collected - download final documents
|
|
86
|
+
# packet = Anvil::Signature.find(webhook.packet_eid)
|
|
87
|
+
# download_signed_documents(packet)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def handle_workflow_created(webhook)
|
|
91
|
+
puts '๐ง Workflow created'
|
|
92
|
+
puts " Workflow ID: #{webhook.workflow_eid}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def handle_workflow_complete(webhook)
|
|
96
|
+
puts 'โ
Workflow complete!'
|
|
97
|
+
puts " Workflow ID: #{webhook.workflow_eid}"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def handle_webform_complete(webhook)
|
|
101
|
+
puts '๐ Webform completed'
|
|
102
|
+
puts " Webform ID: #{webhook.webform_eid}"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def handle_document_group_created(webhook)
|
|
106
|
+
puts '๐ Document group created'
|
|
107
|
+
data = webhook.data
|
|
108
|
+
puts " Group ID: #{data[:documentGroupEid]}" if data[:documentGroupEid]
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def handle_test_webhook(_webhook)
|
|
112
|
+
puts '๐งช Test webhook received!'
|
|
113
|
+
puts ' Your webhook endpoint is working correctly'
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Example: Simple webhook server for testing
|
|
118
|
+
class WebhookServer
|
|
119
|
+
def initialize(port: 4567)
|
|
120
|
+
@port = port
|
|
121
|
+
@handler = WebhookHandler.new
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def start
|
|
125
|
+
server = WEBrick::HTTPServer.new(Port: @port)
|
|
126
|
+
|
|
127
|
+
# Mount webhook endpoint
|
|
128
|
+
server.mount_proc '/webhooks/anvil' do |req, res|
|
|
129
|
+
# Parse the webhook
|
|
130
|
+
webhook = Anvil::Webhook.new(
|
|
131
|
+
payload: req.body,
|
|
132
|
+
token: req.query['token'] || req.header['x-anvil-token']
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Verify the webhook
|
|
136
|
+
if webhook.valid?
|
|
137
|
+
puts 'โ
Webhook verified successfully'
|
|
138
|
+
|
|
139
|
+
# Handle encrypted data if present
|
|
140
|
+
if webhook.encrypted?
|
|
141
|
+
puts '๐ Webhook data is encrypted'
|
|
142
|
+
# To decrypt, you need your RSA private key:
|
|
143
|
+
# decrypted_data = webhook.decrypt('/path/to/private_key.pem')
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Process the webhook
|
|
147
|
+
@handler.handle(webhook)
|
|
148
|
+
|
|
149
|
+
# Return success response
|
|
150
|
+
res.status = 204 # No Content
|
|
151
|
+
else
|
|
152
|
+
puts 'โ Invalid webhook token!'
|
|
153
|
+
res.status = 401 # Unauthorized
|
|
154
|
+
res.body = 'Invalid token'
|
|
155
|
+
end
|
|
156
|
+
rescue Anvil::WebhookError => e
|
|
157
|
+
puts "โ Webhook error: #{e.message}"
|
|
158
|
+
res.status = 400
|
|
159
|
+
res.body = e.message
|
|
160
|
+
rescue StandardError => e
|
|
161
|
+
puts "โ Unexpected error: #{e.message}"
|
|
162
|
+
res.status = 500
|
|
163
|
+
res.body = 'Internal server error'
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Test endpoint
|
|
167
|
+
server.mount_proc '/test' do |_req, res|
|
|
168
|
+
res.body = 'Webhook server is running!'
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
puts "๐ Webhook server running on http://localhost:#{@port}"
|
|
172
|
+
puts "๐ Webhook endpoint: http://localhost:#{@port}/webhooks/anvil"
|
|
173
|
+
puts ' Configure this URL in your Anvil account settings'
|
|
174
|
+
puts "\nPress Ctrl+C to stop the server"
|
|
175
|
+
|
|
176
|
+
trap('INT') { server.shutdown }
|
|
177
|
+
server.start
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Example: Rails controller for webhooks
|
|
182
|
+
def rails_controller_example
|
|
183
|
+
puts <<~RUBY
|
|
184
|
+
# app/controllers/anvil_webhooks_controller.rb
|
|
185
|
+
class AnvilWebhooksController < ApplicationController
|
|
186
|
+
skip_before_action :verify_authenticity_token
|
|
187
|
+
|
|
188
|
+
def create
|
|
189
|
+
webhook = Anvil::Webhook.new(
|
|
190
|
+
payload: request.body.read,
|
|
191
|
+
token: params[:token]
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
if webhook.valid?
|
|
195
|
+
process_webhook(webhook)
|
|
196
|
+
head :no_content
|
|
197
|
+
else
|
|
198
|
+
Rails.logger.error "Invalid Anvil webhook token"
|
|
199
|
+
head :unauthorized
|
|
200
|
+
end
|
|
201
|
+
rescue Anvil::WebhookError => e
|
|
202
|
+
Rails.logger.error "Webhook error: \#{e.message}"
|
|
203
|
+
head :bad_request
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
private
|
|
207
|
+
|
|
208
|
+
def process_webhook(webhook)
|
|
209
|
+
# Process webhook based on action
|
|
210
|
+
case webhook.action
|
|
211
|
+
when 'signerComplete'
|
|
212
|
+
SignerCompleteJob.perform_later(webhook.data)
|
|
213
|
+
when 'etchPacketComplete'
|
|
214
|
+
PacketCompleteJob.perform_later(webhook.data)
|
|
215
|
+
# ... handle other webhook types
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# config/routes.rb
|
|
221
|
+
post 'webhooks/anvil', to: 'anvil_webhooks#create'
|
|
222
|
+
RUBY
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Example: Create a test webhook
|
|
226
|
+
def create_test_webhook
|
|
227
|
+
puts "\n๐งช Creating test webhook..."
|
|
228
|
+
|
|
229
|
+
webhook = Anvil::Webhook.create_test(
|
|
230
|
+
action: 'signerComplete',
|
|
231
|
+
data: {
|
|
232
|
+
signerEid: 'test_signer_123',
|
|
233
|
+
packetEid: 'test_packet_456',
|
|
234
|
+
signerName: 'Test User',
|
|
235
|
+
signerEmail: 'test@example.com'
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
puts 'Test webhook created:'
|
|
240
|
+
puts " Action: #{webhook.action}"
|
|
241
|
+
puts " Valid: #{webhook.valid?}"
|
|
242
|
+
|
|
243
|
+
webhook
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Run the example
|
|
247
|
+
if __FILE__ == $PROGRAM_NAME
|
|
248
|
+
puts '=' * 50
|
|
249
|
+
puts 'Anvil Webhook Verification Example'
|
|
250
|
+
puts '=' * 50
|
|
251
|
+
|
|
252
|
+
choice = ARGV[0]
|
|
253
|
+
|
|
254
|
+
case choice
|
|
255
|
+
when 'server'
|
|
256
|
+
# Start webhook server
|
|
257
|
+
server = WebhookServer.new(port: 4567)
|
|
258
|
+
server.start
|
|
259
|
+
|
|
260
|
+
when 'test'
|
|
261
|
+
# Create and verify a test webhook
|
|
262
|
+
webhook = create_test_webhook
|
|
263
|
+
handler = WebhookHandler.new
|
|
264
|
+
handler.handle(webhook)
|
|
265
|
+
|
|
266
|
+
when 'rails'
|
|
267
|
+
# Show Rails controller example
|
|
268
|
+
puts "\n๐ฑ Rails Controller Example:"
|
|
269
|
+
puts '=' * 40
|
|
270
|
+
rails_controller_example
|
|
271
|
+
|
|
272
|
+
else
|
|
273
|
+
puts "\nUsage:"
|
|
274
|
+
puts " ruby #{__FILE__} server # Start webhook test server"
|
|
275
|
+
puts " ruby #{__FILE__} test # Create and handle test webhook"
|
|
276
|
+
puts " ruby #{__FILE__} rails # Show Rails controller example"
|
|
277
|
+
puts "\nEnvironment variables:"
|
|
278
|
+
puts ' ANVIL_API_KEY - Your Anvil API key'
|
|
279
|
+
puts ' ANVIL_WEBHOOK_TOKEN - Your webhook verification token'
|
|
280
|
+
end
|
|
281
|
+
end
|