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.
@@ -0,0 +1,232 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Create an Anvil E-Signature Packet
5
+ # Based on the official Anvil API documentation
6
+
7
+ $LOAD_PATH.unshift File.expand_path('lib', __dir__)
8
+ require 'anvil'
9
+ require 'anvil/env_loader'
10
+ require 'net/http'
11
+ require 'uri'
12
+ require 'json'
13
+ require 'base64'
14
+
15
+ # Load .env file
16
+ Anvil::EnvLoader.load(File.expand_path('.env', __dir__))
17
+
18
+ puts '=' * 50
19
+ puts 'Anvil E-Signature Packet Creation'
20
+ puts '=' * 50
21
+
22
+ api_key = ENV.fetch('ANVIL_API_KEY', nil)
23
+ puts "\nUsing API Key: #{api_key[0..10]}..."
24
+
25
+ # Step 1: Create a simple PDF to use for signing
26
+ def create_test_pdf(api_key)
27
+ puts "\nšŸ“„ Creating a test PDF document..."
28
+
29
+ uri = URI('https://app.useanvil.com/api/v1/generate-pdf')
30
+ http = Net::HTTP.new(uri.host, uri.port)
31
+ http.use_ssl = true
32
+
33
+ request = Net::HTTP::Post.new(uri.path)
34
+ request.basic_auth(api_key, '')
35
+ request['Content-Type'] = 'application/json'
36
+
37
+ html = <<~HTML
38
+ <!DOCTYPE html>
39
+ <html>
40
+ <body style="padding: 40px; font-family: Arial;">
41
+ <h1>Test Agreement</h1>
42
+ <p>This is a test document for e-signature.</p>
43
+
44
+ <p>I, the undersigned, agree to the terms of this test agreement.</p>
45
+
46
+ <div style="margin-top: 100px; border-top: 2px solid black; width: 300px; padding-top: 10px;">
47
+ Signature
48
+ </div>
49
+
50
+ <div style="margin-top: 50px; border-top: 2px solid black; width: 300px; padding-top: 10px;">
51
+ Date
52
+ </div>
53
+ </body>
54
+ </html>
55
+ HTML
56
+
57
+ request.body = {
58
+ type: 'html',
59
+ data: {
60
+ html: html,
61
+ css: 'body { font-size: 14px; }'
62
+ }
63
+ }.to_json
64
+
65
+ response = http.request(request)
66
+
67
+ if response.code == '200'
68
+ puts ' āœ… PDF created successfully'
69
+ response.body # Return the PDF binary data
70
+ else
71
+ puts " āŒ Failed to create PDF: #{response.code}"
72
+ nil
73
+ end
74
+ end
75
+
76
+ # Step 2: Create an e-signature packet using REST API (simpler than GraphQL)
77
+ def create_etch_packet_rest(api_key, _pdf_data = nil)
78
+ puts "\nšŸ“ Creating e-signature packet via REST API..."
79
+
80
+ # For now, create a simple packet without a file
81
+ # In production, you'd upload the PDF or use a template
82
+
83
+ uri = URI('https://app.useanvil.com/api/v1/etch-packets')
84
+ http = Net::HTTP.new(uri.host, uri.port)
85
+ http.use_ssl = true
86
+
87
+ request = Net::HTTP::Post.new(uri.path)
88
+ request.basic_auth(api_key, '')
89
+ request['Content-Type'] = 'application/json'
90
+
91
+ # Basic packet structure
92
+ packet_data = {
93
+ name: "Test Agreement #{Time.now.strftime('%Y-%m-%d %H:%M')}",
94
+ isDraft: false,
95
+ isTest: true, # Test mode - won't send real emails
96
+ signers: [
97
+ {
98
+ id: 'signer1',
99
+ name: 'Test User',
100
+ email: 'test@example.com',
101
+ signerType: 'email'
102
+ }
103
+ ]
104
+ }
105
+
106
+ request.body = packet_data.to_json
107
+
108
+ response = http.request(request)
109
+ result = begin
110
+ JSON.parse(response.body)
111
+ rescue StandardError
112
+ response.body
113
+ end
114
+
115
+ if %w[200 201].include?(response.code)
116
+ puts ' āœ… Packet created successfully!'
117
+ if result.is_a?(Hash)
118
+ puts " Packet ID: #{result['eid'] || result['id']}"
119
+ puts " Status: #{result['status']}"
120
+ end
121
+ result
122
+ else
123
+ puts " āŒ Failed to create packet: #{response.code}"
124
+ puts " Response: #{result}"
125
+ nil
126
+ end
127
+ rescue StandardError => e
128
+ puts " āŒ Error: #{e.message}"
129
+ nil
130
+ end
131
+
132
+ # Step 3: Try GraphQL mutation with correct structure
133
+ def create_etch_packet_graphql(api_key)
134
+ puts "\nšŸ“ Creating e-signature packet via GraphQL..."
135
+
136
+ uri = URI('https://app.useanvil.com/graphql')
137
+ http = Net::HTTP.new(uri.host, uri.port)
138
+ http.use_ssl = true
139
+
140
+ request = Net::HTTP::Post.new(uri.path)
141
+ request.basic_auth(api_key, '')
142
+ request['Content-Type'] = 'application/json'
143
+
144
+ # GraphQL mutation based on docs
145
+ mutation = {
146
+ query: <<~GRAPHQL,
147
+ mutation CreateEtchPacket($variables: JSON) {
148
+ createEtchPacket(variables: $variables) {
149
+ eid
150
+ name
151
+ status
152
+ }
153
+ }
154
+ GRAPHQL
155
+ variables: {
156
+ variables: {
157
+ name: "Test Agreement via GraphQL #{Time.now.to_i}",
158
+ isDraft: false,
159
+ isTest: true,
160
+ signers: [
161
+ {
162
+ id: 'signer1',
163
+ name: 'Test Signer',
164
+ email: 'test@example.com',
165
+ signerType: 'email'
166
+ }
167
+ ]
168
+ }
169
+ }
170
+ }
171
+
172
+ request.body = mutation.to_json
173
+
174
+ response = http.request(request)
175
+ result = begin
176
+ JSON.parse(response.body)
177
+ rescue StandardError
178
+ response.body
179
+ end
180
+
181
+ if response.code == '200'
182
+ if result['data'] && result['data']['createEtchPacket']
183
+ packet = result['data']['createEtchPacket']
184
+ puts ' āœ… Packet created via GraphQL!'
185
+ puts " EID: #{packet['eid']}"
186
+ puts " Name: #{packet['name']}"
187
+ puts " Status: #{packet['status']}"
188
+ packet
189
+ else
190
+ puts ' āš ļø GraphQL returned no data'
191
+ puts " Errors: #{result['errors']}" if result['errors']
192
+ nil
193
+ end
194
+ else
195
+ puts " āŒ GraphQL request failed: #{response.code}"
196
+ puts " Response: #{result}"
197
+ nil
198
+ end
199
+ rescue StandardError => e
200
+ puts " āŒ Error: #{e.message}"
201
+ nil
202
+ end
203
+
204
+ # Main execution
205
+ puts "\nAttempting to create an e-signature packet..."
206
+
207
+ # Try REST API first (usually simpler)
208
+ packet = create_etch_packet_rest(api_key)
209
+
210
+ # Also try GraphQL
211
+ graphql_packet = create_etch_packet_graphql(api_key)
212
+
213
+ puts "\n#{'=' * 50}"
214
+ puts 'Summary'
215
+ puts '=' * 50
216
+
217
+ if packet || graphql_packet
218
+ puts 'āœ… Successfully created e-signature packet!'
219
+ puts "\nšŸ“š Next steps:"
220
+ puts '1. Log into Anvil: https://app.useanvil.com'
221
+ puts '2. Navigate to the Etch section'
222
+ puts '3. View your test packets'
223
+ puts '4. Get signing URLs for the signers'
224
+ puts "\nšŸ’” Note: Since we used isTest: true, no emails were sent"
225
+ else
226
+ puts "āš ļø Couldn't create a signature packet"
227
+ puts "\nšŸ’” Troubleshooting:"
228
+ puts '1. Check if your API key has e-signature permissions'
229
+ puts '2. Try creating a packet manually in the Anvil UI first'
230
+ puts '3. Check the Anvil documentation for required fields'
231
+ puts ' https://www.useanvil.com/docs/api/e-signatures'
232
+ end
data/debug_env.rb ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift File.expand_path('lib', __dir__)
5
+ require 'anvil/env_loader'
6
+
7
+ puts 'Before loading .env:'
8
+ puts "ANVIL_API_KEY = #{ENV['ANVIL_API_KEY'].inspect}"
9
+
10
+ path = File.expand_path('.env', __dir__)
11
+ puts "\nLoading from: #{path}"
12
+ puts "File exists? #{File.exist?(path)}"
13
+
14
+ if File.exist?(path)
15
+ puts "\n.env contents:"
16
+ File.readlines(path).each_with_index do |line, i|
17
+ puts " Line #{i + 1}: #{line.inspect}"
18
+ if line =~ /\A([A-Z_][A-Z0-9_]*)\s*=\s*(.*)\z/
19
+ puts " -> Matched! Key: #{Regexp.last_match(1)}, Value: #{Regexp.last_match(2)[0..20]}..."
20
+ end
21
+ end
22
+ end
23
+
24
+ puts "\nCalling Anvil::EnvLoader.load..."
25
+ Anvil::EnvLoader.load(path)
26
+
27
+ puts "\nAfter loading .env:"
28
+ puts "ANVIL_API_KEY = #{ENV['ANVIL_API_KEY'] ? "#{ENV['ANVIL_API_KEY'][0..15]}..." : 'NOT SET'}"
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'anvil'
6
+
7
+ # Example: Create and manage e-signature packets
8
+ #
9
+ # This example demonstrates how to:
10
+ # 1. Create an e-signature packet
11
+ # 2. Add signers
12
+ # 3. Generate signing URLs
13
+ # 4. Check signature status
14
+
15
+ # Configure Anvil
16
+ Anvil.configure do |config|
17
+ config.api_key = ENV.fetch('ANVIL_API_KEY', nil)
18
+ config.environment = :development
19
+ end
20
+
21
+ # Example: Create a signature packet for an employment agreement
22
+ def create_employment_agreement
23
+ puts 'šŸ“ Creating e-signature packet for employment agreement...'
24
+
25
+ # Create the signature packet
26
+ packet = Anvil::Signature.create(
27
+ name: 'Employment Agreement - John Doe',
28
+
29
+ # Define signers
30
+ signers: [
31
+ {
32
+ name: 'John Doe',
33
+ email: 'john.doe@example.com',
34
+ role: 'employee',
35
+ signer_type: 'email' # Email-based signing
36
+ },
37
+ {
38
+ name: 'Jane Smith',
39
+ email: 'jane.smith@company.com',
40
+ role: 'hr_manager',
41
+ signer_type: 'email'
42
+ }
43
+ ],
44
+
45
+ # Specify files to sign (using a PDF template)
46
+ files: [
47
+ {
48
+ type: :pdf,
49
+ id: 'your_template_id_here' # Replace with your PDF template ID
50
+ }
51
+ ],
52
+
53
+ # Optional: Set as draft first (signers won't be notified yet)
54
+ is_draft: true,
55
+
56
+ # Optional: Custom email settings
57
+ email_subject: 'Please sign your employment agreement',
58
+ email_body: 'Dear {{signerName}}, Please review and sign the attached employment agreement. Thank you!',
59
+
60
+ # Optional: Webhook URL for status updates
61
+ webhook_url: 'https://yourapp.com/webhooks/anvil'
62
+ )
63
+
64
+ puts 'āœ… Signature packet created!'
65
+ puts "šŸ“‹ Packet ID: #{packet.eid}"
66
+ puts "šŸ“Š Status: #{packet.status}"
67
+ puts "šŸ‘„ Signers: #{packet.signers.count}"
68
+
69
+ packet
70
+ end
71
+
72
+ # Example: Get signing URLs for signers
73
+ def generate_signing_urls(packet)
74
+ puts "\nšŸ”— Generating signing URLs..."
75
+
76
+ packet.signers.each do |signer|
77
+ url = signer.signing_url(
78
+ client_user_id: "user_#{signer.email}" # Optional: Your internal user ID
79
+ )
80
+
81
+ puts "\nšŸ‘¤ Signer: #{signer.name} (#{signer.email})"
82
+ puts "šŸ“Š Status: #{signer.status}"
83
+ puts "šŸ”— Signing URL: #{url}"
84
+ puts ' Send this URL to the signer to complete their signature'
85
+ end
86
+ end
87
+
88
+ # Example: Check signature packet status
89
+ def check_packet_status(packet_eid)
90
+ puts "\nšŸ” Checking packet status..."
91
+
92
+ # Reload the packet to get latest status
93
+ packet = Anvil::Signature.find(packet_eid)
94
+
95
+ puts "šŸ“‹ Packet: #{packet.name}"
96
+ puts "šŸ“Š Overall Status: #{packet.status}"
97
+
98
+ # Check individual signer status
99
+ packet.signers.each do |signer|
100
+ status_emoji = case signer.status
101
+ when 'complete' then 'āœ…'
102
+ when 'sent' then 'šŸ“§'
103
+ when 'viewed' then 'šŸ‘€'
104
+ else 'ā³'
105
+ end
106
+
107
+ puts "#{status_emoji} #{signer.name}: #{signer.status}"
108
+
109
+ puts " Completed at: #{signer.completed_at}" if signer.complete?
110
+ end
111
+
112
+ # Check if entire packet is complete
113
+ if packet.complete?
114
+ puts "\nšŸŽ‰ All signatures collected!"
115
+ # You can now download the signed documents
116
+ elsif packet.partially_complete?
117
+ puts "\nā³ Waiting for remaining signatures..."
118
+ end
119
+
120
+ packet
121
+ end
122
+
123
+ # Example: List all signature packets
124
+ def list_signature_packets
125
+ puts "\nšŸ“‘ Listing signature packets..."
126
+
127
+ packets = Anvil::Signature.list(
128
+ limit: 10,
129
+ status: 'sent' # Filter by status (optional)
130
+ )
131
+
132
+ if packets.empty?
133
+ puts 'No signature packets found'
134
+ else
135
+ packets.each do |packet|
136
+ puts "\nšŸ“‹ #{packet.name}"
137
+ puts " ID: #{packet.eid}"
138
+ puts " Status: #{packet.status}"
139
+ puts " Created: #{packet.created_at}"
140
+ end
141
+ end
142
+ end
143
+
144
+ # Example: Send reminders (by recreating signing URLs)
145
+ def send_reminder(packet)
146
+ puts "\nšŸ“§ Sending reminders to incomplete signers..."
147
+
148
+ packet.signers.each do |signer|
149
+ next if signer.complete?
150
+
151
+ url = signer.signing_url
152
+ puts "šŸ”” Reminder for #{signer.name} (#{signer.email})"
153
+ puts " Signing URL: #{url}"
154
+
155
+ # In a real application, you would send an email here
156
+ # using your email service (ActionMailer, SendGrid, etc.)
157
+ end
158
+ end
159
+
160
+ # Run the example
161
+ begin
162
+ puts '=' * 50
163
+ puts 'Anvil E-Signature Example'
164
+ puts '=' * 50
165
+
166
+ # Create a new signature packet
167
+ packet = create_employment_agreement
168
+
169
+ # Generate signing URLs
170
+ generate_signing_urls(packet)
171
+
172
+ # Simulate checking status after some time
173
+ puts "\nā° (In a real app, you'd check this later...)"
174
+ check_packet_status(packet.eid)
175
+
176
+ # List all packets
177
+ list_signature_packets
178
+
179
+ # Send reminders if needed
180
+ send_reminder(packet) if packet.in_progress?
181
+
182
+ puts "\nāœ… E-signature example completed!"
183
+ rescue Anvil::ValidationError => e
184
+ puts "āŒ Validation error: #{e.message}"
185
+ puts "Errors: #{e.errors.inspect}" if e.errors.any?
186
+ rescue Anvil::AuthenticationError => e
187
+ puts "āŒ Authentication failed: #{e.message}"
188
+ puts 'Please check your API key'
189
+ rescue Anvil::Error => e
190
+ puts "āŒ Anvil error: #{e.message}"
191
+ rescue StandardError => e
192
+ puts "āŒ Unexpected error: #{e.message}"
193
+ puts e.backtrace.first(5)
194
+ end
@@ -0,0 +1,89 @@
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
+
9
+ # Example: Fill a PDF template with data
10
+ #
11
+ # This example demonstrates how to fill a PDF template with JSON data.
12
+ # You'll need:
13
+ # 1. An Anvil API key (set as ANVIL_API_KEY environment variable)
14
+ # 2. A PDF template ID from your Anvil account
15
+
16
+ # Configure Anvil (optional if using ENV var)
17
+ Anvil.configure do |config|
18
+ config.api_key = ENV.fetch('ANVIL_API_KEY', nil)
19
+ config.environment = :development # Use development for testing
20
+ end
21
+
22
+ # Template ID - replace with your actual template ID
23
+ TEMPLATE_ID = 'your_template_id_here'
24
+
25
+ # Sample data to fill the PDF
26
+ # Keys should match the field aliases in your PDF template
27
+ pdf_data = {
28
+ # Basic fields
29
+ name: 'John Doe',
30
+ email: 'john.doe@example.com',
31
+ phone: '(555) 123-4567',
32
+
33
+ # Address
34
+ address: '123 Main Street',
35
+ city: 'San Francisco',
36
+ state: 'CA',
37
+ zip_code: '94102',
38
+
39
+ # Date fields
40
+ date: Date.today.strftime('%B %d, %Y'),
41
+
42
+ # Checkboxes (use true/false)
43
+ agree_to_terms: true,
44
+ subscribe_newsletter: false,
45
+
46
+ # Additional fields (customize based on your template)
47
+ company: 'Acme Corp',
48
+ position: 'Software Engineer',
49
+ salary: '$120,000',
50
+ start_date: '2024-02-01'
51
+ }
52
+
53
+ begin
54
+ puts "Filling PDF template: #{TEMPLATE_ID}"
55
+ puts "With data: #{pdf_data.keys.join(', ')}"
56
+
57
+ # Fill the PDF
58
+ pdf = Anvil::PDF.fill(
59
+ template_id: TEMPLATE_ID,
60
+ data: pdf_data,
61
+ # Optional parameters
62
+ title: 'Employment Agreement',
63
+ font_size: 10,
64
+ text_color: '#333333'
65
+ )
66
+
67
+ # Save the filled PDF
68
+ filename = "filled_pdf_#{Time.now.to_i}.pdf"
69
+ pdf.save_as(filename)
70
+
71
+ puts 'āœ… PDF filled successfully!'
72
+ puts "šŸ“„ Saved as: #{filename}"
73
+ puts "šŸ“ Size: #{pdf.size_human}"
74
+
75
+ # You can also get the PDF as base64 for storing in a database
76
+ # base64_pdf = pdf.to_base64
77
+ # puts "Base64 length: #{base64_pdf.length}" if base64_pdf
78
+ rescue Anvil::ValidationError => e
79
+ puts "āŒ Validation error: #{e.message}"
80
+ puts "Errors: #{e.errors.inspect}" if e.errors.any?
81
+ rescue Anvil::AuthenticationError => e
82
+ puts "āŒ Authentication failed: #{e.message}"
83
+ puts 'Please check your API key'
84
+ rescue Anvil::Error => e
85
+ puts "āŒ Anvil error: #{e.message}"
86
+ rescue StandardError => e
87
+ puts "āŒ Unexpected error: #{e.message}"
88
+ puts e.backtrace.first(5)
89
+ end