assinafy 1.5.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,274 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Assinafy
4
+ module Resources
5
+ # Field definitions (reusable input fields) and per-value validation.
6
+ #
7
+ # See https://api.assinafy.com.br/v1/docs#field-definition for the
8
+ # documentation of these endpoints.
9
+ class FieldResource < BaseResource
10
+ # Create a field definition.
11
+ #
12
+ # @param payload [Hash]
13
+ # @option payload [String] :type required — e.g. `text`, `cpf`, `email` — see {#types}
14
+ # @option payload [String] :name required — display label
15
+ # @option payload [String] :regex optional validation regex (text fields)
16
+ # @option payload [Boolean] :is_required default `true`
17
+ # @param account_id_override [String, nil]
18
+ # @return [Hash] the created field definition (envelope `data` unwrapped)
19
+ # @see POST /accounts/{accountId}/fields
20
+ # @example Create a text field
21
+ # field = client.fields.create(type: 'text', name: 'audit-field-1780694479')
22
+ #
23
+ # # Request body the SDK sends:
24
+ # # { "type": "text", "name": "audit-field-1780694479" }
25
+ #
26
+ # # => {
27
+ # # "resource" => "field_definition",
28
+ # # "id" => "1032009e858cc1f859ccf3a61229",
29
+ # # "name" => "audit-field-1780694479",
30
+ # # "type" => "text",
31
+ # # "regex" => nil,
32
+ # # "is_pre_defined" => false,
33
+ # # "is_active" => true,
34
+ # # "is_required" => true,
35
+ # # "is_standard" => false,
36
+ # # "is_read_only" => false,
37
+ # # "is_visible" => true
38
+ # # }
39
+ def create(payload, account_id_override = nil)
40
+ acc_id = account_id(account_id_override)
41
+ body = body_params(require_payload(payload))
42
+
43
+ call('Failed to create field definition') do
44
+ http_post("accounts/#{acc_id}/fields", body)
45
+ end
46
+ end
47
+
48
+ # List field definitions.
49
+ #
50
+ # NOTE: this endpoint does not send pagination headers, so `meta` is always
51
+ # `nil` here (unlike other list endpoints which return a pagination Hash).
52
+ #
53
+ # @param params [Hash] `include_inactive`, `include_standard`
54
+ # @param account_id_override [String, nil]
55
+ # @return [Hash{Symbol=>Array,nil}] `{ data: [...], meta: nil }`
56
+ # @see GET /accounts/{accountId}/fields
57
+ # @example List including inactive fields
58
+ # result = client.fields.list(include_inactive: true)
59
+ #
60
+ # # => {
61
+ # # data: [
62
+ # # {
63
+ # # "id" => "field-id",
64
+ # # "name" => "Nome",
65
+ # # "type" => "personName",
66
+ # # "regex" => nil,
67
+ # # "is_pre_defined" => true,
68
+ # # "is_active" => true,
69
+ # # "is_required" => false,
70
+ # # "is_standard" => false,
71
+ # # "is_read_only" => false,
72
+ # # "is_visible" => true
73
+ # # }
74
+ # # # ... (one Hash per field definition)
75
+ # # ],
76
+ # # meta: nil # this endpoint sends no pagination headers
77
+ # # }
78
+ def list(params = {}, account_id_override = nil)
79
+ acc_id = account_id(account_id_override)
80
+
81
+ call_list('Failed to list field definitions') do
82
+ http_get("accounts/#{acc_id}/fields", params)
83
+ end
84
+ end
85
+
86
+ # Fetch a field definition by ID.
87
+ #
88
+ # @param field_id [String]
89
+ # @param account_id_override [String, nil]
90
+ # @return [Hash] the field definition (envelope `data` unwrapped)
91
+ # @see GET /accounts/{accountId}/fields/{field_id}
92
+ # @example Fetch one field definition
93
+ # field = client.fields.get('1032009e858cc1f859ccf3a61229')
94
+ #
95
+ # # => {
96
+ # # "resource" => "field_definition",
97
+ # # "id" => "1032009e858cc1f859ccf3a61229",
98
+ # # "name" => "audit-field-1780694479",
99
+ # # "type" => "text",
100
+ # # "regex" => nil,
101
+ # # "is_pre_defined" => false,
102
+ # # "is_active" => true,
103
+ # # "is_required" => true,
104
+ # # "is_standard" => false,
105
+ # # "is_read_only" => false,
106
+ # # "is_visible" => true
107
+ # # }
108
+ def get(field_id, account_id_override = nil)
109
+ acc_id = account_id(account_id_override)
110
+ fid = require_id(field_id, 'Field ID')
111
+
112
+ call('Failed to fetch field definition') do
113
+ http_get("accounts/#{acc_id}/fields/#{fid}")
114
+ end
115
+ end
116
+
117
+ # Update a field definition. The update endpoint accepts only `name`,
118
+ # `regex`, and `is_active` (unlike {#create}, it does not accept `type`
119
+ # or `is_required`).
120
+ #
121
+ # @param field_id [String]
122
+ # @param payload [Hash]
123
+ # @option payload [String] :name display label
124
+ # @option payload [String] :regex validation regex (text fields)
125
+ # @option payload [Boolean] :is_active enable/disable the field
126
+ # @param account_id_override [String, nil]
127
+ # @return [Hash] the updated field definition (envelope `data` unwrapped)
128
+ # @see PUT /accounts/{account_id}/fields/{field_id}
129
+ # @example Rename a field definition
130
+ # field = client.fields.update('1032009e858cc1f859ccf3a61229', name: 'New Field Name')
131
+ #
132
+ # # Request body the SDK sends:
133
+ # # { "name": "New Field Name" }
134
+ #
135
+ # # => {
136
+ # # "resource" => "field_definition",
137
+ # # "id" => "1032009e858cc1f859ccf3a61229",
138
+ # # "name" => "New Field Name",
139
+ # # "type" => "text",
140
+ # # "regex" => nil,
141
+ # # "is_pre_defined" => false,
142
+ # # "is_active" => true,
143
+ # # "is_required" => true,
144
+ # # "is_standard" => false,
145
+ # # "is_read_only" => false,
146
+ # # "is_visible" => true
147
+ # # }
148
+ def update(field_id, payload, account_id_override = nil)
149
+ acc_id = account_id(account_id_override)
150
+ fid = require_id(field_id, 'Field ID')
151
+ raw = require_payload(payload)
152
+ body = body_params(raw)
153
+ body['regex'] = nil if (raw.key?(:regex) && raw[:regex].nil?) ||
154
+ (raw.key?('regex') && raw['regex'].nil?)
155
+
156
+ call('Failed to update field definition') do
157
+ http_put("accounts/#{acc_id}/fields/#{fid}", body)
158
+ end
159
+ end
160
+
161
+ # Delete a field definition. Fields used by any document cannot be deleted.
162
+ #
163
+ # @param field_id [String]
164
+ # @param account_id_override [String, nil]
165
+ # @return [nil] the API returns `data: []`; the SDK normalizes this to `nil`
166
+ # @see DELETE /accounts/{account_id}/fields/{field_id}
167
+ # @example Delete a field definition
168
+ # client.fields.delete('1032009e858cc1f859ccf3a61229')
169
+ # # => nil
170
+ def delete(field_id, account_id_override = nil)
171
+ acc_id = account_id(account_id_override)
172
+ fid = require_id(field_id, 'Field ID')
173
+
174
+ call_void('Failed to delete field definition') do
175
+ http_delete("accounts/#{acc_id}/fields/#{fid}")
176
+ end
177
+ end
178
+
179
+ # Validate a single value against a field definition.
180
+ #
181
+ # Either workspace Authorization or signer-access-code authentication
182
+ # works; pass `signer_access_code:` for the latter.
183
+ #
184
+ # @param field_id [String]
185
+ # @param value [Object]
186
+ # @param account_id_override [String, nil]
187
+ # @param signer_access_code [String, nil]
188
+ # @return [Hash{String=>Object}] `{ "type" =>, "success" =>, "error_message" => }`
189
+ # @see POST /accounts/{accountId}/fields/{field_id}/validate
190
+ # @example Validate a value (workspace auth)
191
+ # result = client.fields.validate('1032009e858cc1f859ccf3a61229', 'Some text')
192
+ #
193
+ # # Request body the SDK sends:
194
+ # # { "value": "Some text" }
195
+ #
196
+ # # => { "type" => "text", "success" => true, "error_message" => "" }
197
+ #
198
+ # @example Validate as a signer (signer-access-code auth, sent as a query param)
199
+ # client.fields.validate('field-id', 'Some text', signer_access_code: 'signer-access-code')
200
+ # # => { "type" => "text", "success" => true, "error_message" => "" }
201
+ def validate(field_id, value, account_id_override = nil, signer_access_code: nil)
202
+ acc_id = account_id(account_id_override)
203
+ fid = require_id(field_id, 'Field ID')
204
+
205
+ call('Failed to validate field value') do
206
+ http_post("accounts/#{acc_id}/fields/#{fid}/validate", body_params(value: value),
207
+ signer_access_code: signer_access_code)
208
+ end
209
+ end
210
+
211
+ # Validate many `{ field_id:, value: }` pairs in a single call.
212
+ #
213
+ # @param values [Array<Hash>]
214
+ # @param account_id_override [String, nil]
215
+ # @param signer_access_code [String, nil]
216
+ # @return [Array<Hash>] one validation Hash per input, each carrying its `field_id`
217
+ # @see POST /accounts/{accountId}/fields/validate-multiple
218
+ # @example Validate several values at once
219
+ # results = client.fields.validate_multiple([
220
+ # { field_id: '63488ffb7adf435aba319787', value: '1111111111111' },
221
+ # { field_id: '63488ffb0461cebb70775497', value: 'user@example.com' }
222
+ # ])
223
+ #
224
+ # # Request body the SDK sends (an array, not an object):
225
+ # # [
226
+ # # { "field_id": "63488ffb7adf435aba319787", "value": "1111111111111" },
227
+ # # { "field_id": "63488ffb0461cebb70775497", "value": "user@example.com" }
228
+ # # ]
229
+ #
230
+ # # => [
231
+ # # { "field_id" => "63488ffb7adf435aba319787", "type" => "cpf",
232
+ # # "success" => false, "error_message" => "Invalid CPF." },
233
+ # # { "field_id" => "63488ffb0461cebb70775497", "type" => "email",
234
+ # # "success" => true, "error_message" => "" }
235
+ # # ]
236
+ def validate_multiple(values, account_id_override = nil, signer_access_code: nil)
237
+ acc_id = account_id(account_id_override)
238
+ list = require_array(values, 'Field values')
239
+
240
+ call('Failed to validate field values') do
241
+ http_post("accounts/#{acc_id}/fields/validate-multiple",
242
+ list.map { |item| item.is_a?(Hash) ? body_params(item) : item },
243
+ signer_access_code: signer_access_code)
244
+ end
245
+ end
246
+
247
+ # List the catalog of supported field types.
248
+ #
249
+ # @return [Array<Hash{String=>String}>] each entry is `{ "type" =>, "name" => }`
250
+ # @see GET /field-types
251
+ # @example List supported field types
252
+ # types = client.fields.types
253
+ #
254
+ # # => [
255
+ # # { "type" => "personName", "name" => "Nome" },
256
+ # # { "type" => "cpf", "name" => "CPF" },
257
+ # # { "type" => "phoneNumber", "name" => "Número de Telefone" },
258
+ # # { "type" => "postalCode", "name" => "CEP" },
259
+ # # { "type" => "email", "name" => "E-mail" },
260
+ # # { "type" => "cnpj", "name" => "CNPJ" },
261
+ # # { "type" => "companyName", "name" => "Nome da empresa" },
262
+ # # { "type" => "email", "name" => "E-mail" }, # the live catalog lists "email" twice
263
+ # # { "type" => "text", "name" => "Texto" },
264
+ # # { "type" => "number", "name" => "Número" },
265
+ # # { "type" => "date", "name" => "Data" }
266
+ # # ]
267
+ def types
268
+ call('Failed to list field types') do
269
+ http_get('field-types')
270
+ end
271
+ end
272
+ end
273
+ end
274
+ end
@@ -0,0 +1,222 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Assinafy
4
+ module Resources
5
+ # Signer-authenticated views over a signer's assigned documents.
6
+ #
7
+ # Authentication varies by endpoint (verified against the live API):
8
+ #
9
+ # - {#current}/{#document}, {#sign_multiple}, {#decline_multiple} require the
10
+ # `signer-access-code` URL parameter.
11
+ # - {#list} accepts EITHER the workspace `X-Api-Key` header OR the access
12
+ # code; pass `signer_access_code:` only when authenticating as the signer.
13
+ # - {#download} only needs the document/artifact IDs.
14
+ #
15
+ # See https://api.assinafy.com.br/v1/docs#signer for the full
16
+ # documentation of these endpoints.
17
+ class SignerDocumentResource < BaseResource
18
+ # Fetch the signer's "current" document (the one referenced by the access code).
19
+ #
20
+ # Resolves the document, assignment, and current signer directly from the
21
+ # access code; it does not require code verification or data confirmation.
22
+ # The shape mirrors the signing endpoints, and `assignment.items` is
23
+ # filtered to only the current signer's items.
24
+ #
25
+ # @param signer_id [String]
26
+ # @param signer_access_code [String]
27
+ # @return [Hash] the document (envelope `data` unwrapped)
28
+ # @see GET /signers/{signer_id}/document
29
+ # @example Fetch the document tied to an access code
30
+ # doc = client.signer_documents.current('signer-id', signer_access_code: 'signer-access-code')
31
+ #
32
+ # # => {
33
+ # # "id" => "document-id",
34
+ # # "account_id" => "account-id",
35
+ # # "name" => "my_document.pdf",
36
+ # # "status" => "metadata_ready",
37
+ # # "artifacts" => { "original" => "https://...", "thumbnail" => "https://..." },
38
+ # # "is_closed" => false,
39
+ # # "signing_url" => "https://app.assinafy.com.br/sign/doc1",
40
+ # # "decline_reason" => nil,
41
+ # # "declined_by" => nil,
42
+ # # "created_at" => "2023-07-21T13:43:17Z",
43
+ # # "updated_at" => "2023-07-21T13:43:17Z",
44
+ # # "current_signer" => { "id" => "signer-id", "full_name" => "Signer Name",
45
+ # # "email" => "signer@example.com",
46
+ # # "verification_method" => "Email", "notification_methods" => ["Email"] },
47
+ # # "assignment" => { "id" => "1", "method" => "virtual", "items" => [{ ... }] }
48
+ # # # ... (see docs for full shape)
49
+ # # }
50
+ def current(signer_id, signer_access_code:)
51
+ sid = require_id(signer_id, 'Signer ID')
52
+
53
+ call('Failed to fetch signer document') do
54
+ http_get("signers/#{sid}/document", signer_access_code: signer_access_code)
55
+ end
56
+ end
57
+
58
+ alias document current
59
+
60
+ # List all documents the signer has access to, with pagination metadata.
61
+ # This endpoint accepts either the workspace `X-Api-Key` header or the
62
+ # signer access code, so `signer_access_code:` is optional here; when nil
63
+ # it is omitted from the query and the header auth is used.
64
+ #
65
+ # @param signer_id [String]
66
+ # @param params [Hash] documented `page` and `per_page` query parameters
67
+ # @param signer_access_code [String, nil]
68
+ # @return [Hash{Symbol=>Array,Hash}] `{ data: [...], meta: { current_page:, per_page:, total:, last_page: } }`
69
+ # @see GET /signers/{signer_id}/documents
70
+ # @example List the signer's documents with pagination
71
+ # page = client.signer_documents.list('signer-id',
72
+ # { page: 1, per_page: 15 },
73
+ # signer_access_code: 'signer-access-code')
74
+ #
75
+ # # => {
76
+ # # data: [
77
+ # # {
78
+ # # "id" => "document-id",
79
+ # # "account_id" => "account-id",
80
+ # # "name" => "my_document.pdf",
81
+ # # "status" => "metadata_ready",
82
+ # # "assignment" => { "id" => "1", "method" => "virtual", "signers" => [{ ... }],
83
+ # # "items" => [{ ... }], "summary" => { "signer_count" => 2, ... } },
84
+ # # "artifacts" => { "original" => "https://...", "thumbnail" => "https://..." },
85
+ # # "pages" => [{ "id" => "...", "number" => 1, "height" => 1, "width" => 1,
86
+ # # "download_url" => "https://..." }],
87
+ # # "is_closed" => false
88
+ # # # ... (see docs for full shape)
89
+ # # }
90
+ # # ],
91
+ # # meta: { current_page: 1, per_page: 15, total: 1, last_page: 1 }
92
+ # # }
93
+ def list(signer_id, params = {}, signer_access_code: nil)
94
+ sid = require_id(signer_id, 'Signer ID')
95
+
96
+ call_list('Failed to list signer documents') do
97
+ http_get("signers/#{sid}/documents", params.merge(signer_access_code: signer_access_code))
98
+ end
99
+ end
100
+
101
+ # Lightweight search over the signer's documents. Like {#list}, this
102
+ # accepts either the workspace `X-Api-Key` header or the signer access
103
+ # code, so `signer_access_code:` is optional.
104
+ #
105
+ # @param signer_id [String]
106
+ # @param query [String] free-text search term
107
+ # @param params [Hash] optional deployment-specific query parameters
108
+ # @param signer_access_code [String, nil]
109
+ # @return [Hash{Symbol=>Array,Hash}] `{ data: [...], meta: {..} | nil }`
110
+ # @see GET /signers/{signer_id}/documents/search
111
+ # @example Search the signer's documents
112
+ # page = client.signer_documents.search('signer-id', 'contract')
113
+ #
114
+ # # Request: GET /signers/{signer_id}/documents/search?search=contract
115
+ # # => {
116
+ # # data: [
117
+ # # { "id" => "document-id", "account_id" => "account-id", "name" => "contract.pdf",
118
+ # # "status" => "pending_signature", "template_id" => nil,
119
+ # # "artifacts" => { "original" => "https://...", "thumbnail" => "https://..." },
120
+ # # "is_closed" => false, "signing_url" => "https://...", "tags" => []
121
+ # # # ... search returns the lightweight document shape (no embedded assignment)
122
+ # # }
123
+ # # ],
124
+ # # meta: nil
125
+ # # }
126
+ def search(signer_id, query, params = {}, signer_access_code: nil)
127
+ sid = require_id(signer_id, 'Signer ID')
128
+
129
+ call_list('Failed to search signer documents') do
130
+ http_get("signers/#{sid}/documents/search",
131
+ params.merge(search: query, signer_access_code: signer_access_code))
132
+ end
133
+ end
134
+
135
+ # Sign multiple virtual-method documents in a single call.
136
+ #
137
+ # Each document must be prepared for the "virtual" signature method.
138
+ #
139
+ # @param document_ids [Array<String>]
140
+ # @param signer_access_code [String]
141
+ # @return [Array] empty array on success (envelope `data` unwrapped)
142
+ # @see PUT /signers/documents/sign-multiple
143
+ # @example Sign two documents at once
144
+ # client.signer_documents.sign_multiple(%w[document-1 document-2],
145
+ # signer_access_code: 'signer-access-code')
146
+ #
147
+ # # Request body the SDK sends:
148
+ # # { "document_ids": ["document-1", "document-2"] }
149
+ #
150
+ # # => []
151
+ def sign_multiple(document_ids, signer_access_code:)
152
+ ids = require_array(document_ids, 'Document IDs')
153
+
154
+ call('Failed to sign documents') do
155
+ http_put('signers/documents/sign-multiple',
156
+ body_params(document_ids: ids),
157
+ signer_access_code: signer_access_code)
158
+ end
159
+ end
160
+
161
+ # Decline multiple documents in a single call.
162
+ #
163
+ # @param document_ids [Array<String>]
164
+ # @param decline_reason [String]
165
+ # @param signer_access_code [String]
166
+ # @return [Array] empty array on success (envelope `data` unwrapped)
167
+ # @see PUT /signers/documents/decline-multiple
168
+ # @example Decline two documents with a reason
169
+ # client.signer_documents.decline_multiple(%w[document-1 document-2],
170
+ # decline_reason: 'Unfavorable terms.',
171
+ # signer_access_code: 'signer-access-code')
172
+ #
173
+ # # Request body the SDK sends:
174
+ # # { "document_ids": ["document-1", "document-2"], "decline_reason": "Unfavorable terms." }
175
+ #
176
+ # # => []
177
+ def decline_multiple(document_ids, decline_reason:, signer_access_code:)
178
+ ids = require_array(document_ids, 'Document IDs')
179
+ reason = require_present(decline_reason, 'Decline reason')
180
+
181
+ call('Failed to decline documents') do
182
+ http_put('signers/documents/decline-multiple',
183
+ body_params(document_ids: ids, decline_reason: reason),
184
+ signer_access_code: signer_access_code)
185
+ end
186
+ end
187
+
188
+ # Download an artifact for one of the signer's documents.
189
+ #
190
+ # This endpoint is public (no auth required) — only the document and
191
+ # artifact IDs are needed — so `signer_access_code:` is optional and
192
+ # omitted from the query when nil.
193
+ #
194
+ # @param signer_id [String]
195
+ # @param document_id [String]
196
+ # @param artifact_name [String] `original`, `certificated`, `certificate-page`, `pades`, or
197
+ # `bundle`
198
+ # @param signer_access_code [String, nil] optional
199
+ # @return [String] binary file body (ASCII-8BIT), e.g. the raw PDF bytes
200
+ # @see GET /signers/{signer_id}/documents/{document_id}/download/{artifact_name}
201
+ # @example Download the original PDF and write it to disk (no access code needed)
202
+ # pdf = client.signer_documents.download('signer-id', 'document-id', 'original')
203
+ #
204
+ # # Response is the raw artifact body (Content-Type: application/pdf):
205
+ # # => "%PDF-1.7\n..." (binary string)
206
+ # File.binwrite('document.pdf', pdf)
207
+ def download(signer_id, document_id, artifact_name = 'certificated', signer_access_code: nil)
208
+ sid = require_id(signer_id, 'Signer ID')
209
+ did = require_id(document_id, 'Document ID')
210
+ art = require_id(artifact_name, 'Artifact name')
211
+ unless DocumentResource::ARTIFACT_TYPES.include?(art)
212
+ raise ValidationError.new('Invalid artifact type', { artifact_name: artifact_name })
213
+ end
214
+
215
+ call_binary('Failed to download signer document') do
216
+ http_get("signers/#{sid}/documents/#{did}/download/#{art}",
217
+ signer_access_code: signer_access_code)
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end