assinafy 1.9.0 → 1.10.1
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 +14 -0
- data/README.en.md +39 -12
- data/README.md +57 -25
- data/docs/API_REFERENCE.md +110 -34
- data/lib/assinafy/oauth.rb +29 -15
- data/lib/assinafy/resources/account_resource.rb +42 -1
- data/lib/assinafy/resources/assignment_resource.rb +92 -25
- data/lib/assinafy/resources/auth_resource.rb +55 -1
- data/lib/assinafy/resources/base_resource.rb +6 -3
- data/lib/assinafy/resources/document_resource.rb +98 -11
- data/lib/assinafy/resources/field_resource.rb +29 -0
- data/lib/assinafy/resources/oauth_resource.rb +61 -7
- data/lib/assinafy/resources/signer_document_resource.rb +20 -1
- data/lib/assinafy/resources/signer_resource.rb +37 -6
- data/lib/assinafy/resources/tag_resource.rb +12 -1
- data/lib/assinafy/resources/template_resource.rb +16 -0
- data/lib/assinafy/resources/user_resource.rb +12 -0
- data/lib/assinafy/resources/webhook_resource.rb +20 -2
- data/lib/assinafy/utils.rb +38 -0
- data/lib/assinafy/version.rb +1 -1
- data/sig/assinafy.rbs +4 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d8a12972c20d3f42452948edca772428326d37eafaf2354286bd9d3285bb6bf2
|
|
4
|
+
data.tar.gz: ad4b481584b05d052d7f44cde2a0cc7c99873e127031c30c60662c0a95dbca40
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8990224d37bac65328f95e7bdae28c999381f33fa64f1cfe300f3fffc124e8e98ab4e8e8473802534b1155ec06fc5c8080ffffb8a809118abf8d58cc661ad6a7
|
|
7
|
+
data.tar.gz: 35be652f65fa20a29cef34f3487f4af98ac9e94d613ec51c816b24ee879ed436b4c168aaeb51f32fef81864a9b7e54824812fa713693b2e7235e424ae407f085
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the `assinafy` Ruby gem are documented here.
|
|
4
4
|
|
|
5
|
+
## 1.10.1
|
|
6
|
+
|
|
7
|
+
- Update the pinned Ruby setup action to support Ruby 4.0.7 in CI and release workflows.
|
|
8
|
+
|
|
9
|
+
## 1.10.0
|
|
10
|
+
|
|
11
|
+
- Support the RFC 8693 token-exchange grant for Assinafy-provisioned internal service clients.
|
|
12
|
+
- Validate OAuth grant parameters, PKCE challenges, authorization URLs, and token response shapes.
|
|
13
|
+
- Validate assignment deadlines, notification and verification method combinations, and signing steps before requests.
|
|
14
|
+
- Validate template role and signer IDs, distinct role assignments, and editor field payloads before document creation.
|
|
15
|
+
- Validate contact addresses, scalar payload fields, decline-reason lengths, and finite polling intervals consistently.
|
|
16
|
+
- Expand the document workflow guides, OAuth integration instructions, and API schema reference.
|
|
17
|
+
- Use Ruby 4.0.7 for the development and release toolchain.
|
|
18
|
+
|
|
5
19
|
## 1.9.0
|
|
6
20
|
|
|
7
21
|
- `OAuthResource#refresh`, and `#token` with the `refresh_token` grant, raise `Assinafy::Error` instead of
|
data/README.en.md
CHANGED
|
@@ -57,6 +57,7 @@ client = Assinafy::Client.new(
|
|
|
57
57
|
)
|
|
58
58
|
|
|
59
59
|
document = client.documents.upload({ file_path: './contract.pdf' })
|
|
60
|
+
document = client.documents.wait_until_ready(document.fetch('id'))
|
|
60
61
|
signer = client.signers.create(full_name: 'Alice Silva', email: 'alice@example.com')
|
|
61
62
|
|
|
62
63
|
assignment = client.assignments.create(
|
|
@@ -72,6 +73,7 @@ puts assignment['id']
|
|
|
72
73
|
## Configuration
|
|
73
74
|
|
|
74
75
|
```ruby
|
|
76
|
+
require 'logger'
|
|
75
77
|
client = Assinafy::Client.new(
|
|
76
78
|
api_key: 'your-api-key',
|
|
77
79
|
token: nil,
|
|
@@ -270,6 +272,11 @@ client.oauth.authorization_server_metadata['code_challenge_methods_supported']
|
|
|
270
272
|
`authorization_server_metadata` reaches a different host, so — like `/oauth/token` and `/oauth/revoke` — the SDK
|
|
271
273
|
sends it with no workspace credentials attached.
|
|
272
274
|
|
|
275
|
+
**Internal service clients.** The advertised RFC 8693 token-exchange grant is restricted to
|
|
276
|
+
Assinafy-provisioned confidential service clients. Marketplace apps use PKCE authorization code
|
|
277
|
+
and refresh. `client.oauth.token` accepts token exchange with `client_secret`, `subject_token`,
|
|
278
|
+
`subject_token_type`, and `resource`; it issues no refresh token.
|
|
279
|
+
|
|
273
280
|
**Errors.** OAuth routes answer with the flat RFC 6749 object rather than this API's envelope, so the SDK raises
|
|
274
281
|
`Assinafy::OAuthError` (a subclass of `Assinafy::ApiError`):
|
|
275
282
|
|
|
@@ -343,11 +350,11 @@ client.documents.append_tags('document-id', ['tag-id-3'])
|
|
|
343
350
|
# The deployed sandbox also accepts existing tag names in these arrays.
|
|
344
351
|
client.documents.detach_tag('document-id', 'tag-id')
|
|
345
352
|
|
|
346
|
-
# Template-driven creation
|
|
353
|
+
# Template-driven creation (a template with one configured Signer role)
|
|
347
354
|
client.documents.create_from_template(
|
|
348
355
|
'template-id',
|
|
349
356
|
[{ role_id: 'role', id: 'signer-id', verification_method: 'Email', notification_methods: ['Email'] }],
|
|
350
|
-
{ name: 'Contract', message: 'Please sign', expires_at: '
|
|
357
|
+
{ name: 'Contract', message: 'Please sign', expires_at: '2099-12-31T23:59:00Z' }
|
|
351
358
|
)
|
|
352
359
|
client.documents.estimate_cost_from_template(
|
|
353
360
|
'template-id',
|
|
@@ -401,7 +408,7 @@ client.assignments.create(
|
|
|
401
408
|
method: 'virtual',
|
|
402
409
|
signers: [{ id: 'signer-1', verification_method: 'Email', notification_methods: ['Email'], step: 1 }],
|
|
403
410
|
message: 'Please sign',
|
|
404
|
-
expires_at: '
|
|
411
|
+
expires_at: '2099-12-31T23:59:00Z',
|
|
405
412
|
copy_receivers: ['cc-signer-id']
|
|
406
413
|
)
|
|
407
414
|
|
|
@@ -417,7 +424,7 @@ client.assignments.create(
|
|
|
417
424
|
|
|
418
425
|
client.assignments.list # GET /assignments (scoped to the account)
|
|
419
426
|
client.assignments.estimate_cost('document-id', signers: [{ verification_method: 'Whatsapp' }])
|
|
420
|
-
client.assignments.reset_expiration('document-id', 'assignment-id', '
|
|
427
|
+
client.assignments.reset_expiration('document-id', 'assignment-id', '2099-12-31T23:59:00Z')
|
|
421
428
|
client.assignments.reset_expiration('document-id', 'assignment-id', nil) # clears the expiry
|
|
422
429
|
client.assignments.resend_notification('document-id', 'assignment-id', 'signer-id')
|
|
423
430
|
client.assignments.estimate_resend_cost('document-id', 'assignment-id', 'signer-id')
|
|
@@ -436,6 +443,11 @@ client.assignments.decline('document-id', 'assignment-id', decline_reason: 'Clau
|
|
|
436
443
|
|
|
437
444
|
> The `sign` request body is the API's camelCase body-key exception. This SDK accepts the snake_case keys (`item_id`, `field_id`, `page_id`, `value`) shown above and maps them to `itemId/fieldId/pageId/value` automatically. CamelCase input is also passed through unchanged. Assignment listing separately uses the live-required `accountId` query parameter.
|
|
438
445
|
|
|
446
|
+
Use exactly one notification channel per signer: `Email` verification pairs with `Email`,
|
|
447
|
+
`Whatsapp` pairs with `Whatsapp`, and `DigitalCertificate` permits either. If any signer has a
|
|
448
|
+
`step`, all must have one, with contiguous steps starting at 1. A certificate signer must be alone
|
|
449
|
+
in its step. Deadlines require a timezone and must be at least one hour in the future.
|
|
450
|
+
|
|
439
451
|
### Signer documents (multi-document workflows)
|
|
440
452
|
|
|
441
453
|
```ruby
|
|
@@ -459,7 +471,12 @@ client.templates.delete('template-id')
|
|
|
459
471
|
client.templates.download_page('template-id', 'page-id') # binary image bytes
|
|
460
472
|
```
|
|
461
473
|
|
|
462
|
-
> Template endpoints (`get`/`create`/`update`/`delete`/`download_page`) are
|
|
474
|
+
> Template endpoints (`get`/`create`/`update`/`delete`/`download_page`) are supported by the deployed API but are not part of the current OpenAPI document. `create` requires a source file (`multipart/form-data`); the template name defaults to the uploaded file's name.
|
|
475
|
+
|
|
476
|
+
A template upload initially contains an `Editor` role. Configure at least one `Signer` role and its
|
|
477
|
+
fields in the Assinafy application before generating a document. Supply one entry per role, each with
|
|
478
|
+
a distinct existing signer ID; `role_id` comes from the template's `roles` array. Cost estimation
|
|
479
|
+
requires `role_id` but can omit the signer ID.
|
|
463
480
|
|
|
464
481
|
### Tags
|
|
465
482
|
|
|
@@ -639,16 +656,18 @@ assignment = client.assignments.create(
|
|
|
639
656
|
method: 'virtual',
|
|
640
657
|
signers: [signer_request],
|
|
641
658
|
message: 'Please review and sign.',
|
|
642
|
-
expires_at: '
|
|
659
|
+
expires_at: '2099-12-31T23:59:00Z'
|
|
643
660
|
)
|
|
644
661
|
# => Assignment, including signing_urls
|
|
645
662
|
```
|
|
646
663
|
|
|
647
|
-
|
|
664
|
+
For a template configured with exactly one `Signer` role, the alternative binds the existing signer
|
|
665
|
+
to that role and creates the document and assignment together. Templates with more roles require
|
|
666
|
+
a distinct signer entry for each role:
|
|
648
667
|
|
|
649
668
|
```ruby
|
|
650
669
|
role_signer = {
|
|
651
|
-
role_id: template.fetch('roles').
|
|
670
|
+
role_id: template.fetch('roles').find { |role| role['assignment_type'] == 'Signer' }.fetch('id'),
|
|
652
671
|
id: signer.fetch('id'),
|
|
653
672
|
verification_method: 'Email',
|
|
654
673
|
notification_methods: ['Email']
|
|
@@ -721,7 +740,8 @@ signer is created.
|
|
|
721
740
|
Requesting the method is fully supported. Completing the signature is not: the two-step handshake
|
|
722
741
|
(`/signers/certificate/start`, `/signers/certificate/complete`) is absent from the API v1 machine contract — its
|
|
723
742
|
authentication and request/response schemas are not published — so the SDK does not expose those completion
|
|
724
|
-
calls
|
|
743
|
+
calls. Direct A1/A3 signers to Assinafy's hosted signing flow using the returned `signing_urls`;
|
|
744
|
+
contact Assinafy for a published contract before integrating the handshake directly.
|
|
725
745
|
Once it completes, the `pades` artifact returns the qualified signature.
|
|
726
746
|
|
|
727
747
|
### 7. Inspect, tag, download, and verify
|
|
@@ -734,7 +754,8 @@ tags = client.documents.append_tags(document_id, ['tag-id']) # => Array<Tag>
|
|
|
734
754
|
client.documents.replace_tags(document_id, tags.map { |tag| tag.fetch('id') })
|
|
735
755
|
|
|
736
756
|
original_pdf = client.documents.download(document_id, 'original')
|
|
737
|
-
signed_pdf = client.documents.download(document_id, '
|
|
757
|
+
signed_pdf = client.documents.download(document_id, 'certificated')
|
|
758
|
+
# For documents completed with ICP-Brasil certificates: download(document_id, 'pades')
|
|
738
759
|
certificate_pdf = client.documents.download(document_id, 'certificated')
|
|
739
760
|
|
|
740
761
|
verification = client.documents.verify('signature-hash-from-assinafy')
|
|
@@ -787,10 +808,10 @@ result = client.upload_and_request_signatures(
|
|
|
787
808
|
source: { file_path: './contract.pdf' },
|
|
788
809
|
signers: [
|
|
789
810
|
{ full_name: 'Alice Silva', email: 'alice@example.com' },
|
|
790
|
-
{ full_name: 'Bob Costa',
|
|
811
|
+
{ full_name: 'Bob Costa', email: 'bob@example.com' }
|
|
791
812
|
],
|
|
792
813
|
message: 'Please sign.',
|
|
793
|
-
expires_at: '
|
|
814
|
+
expires_at: '2099-12-31T23:59:00Z'
|
|
794
815
|
)
|
|
795
816
|
|
|
796
817
|
puts result[:document]['id']
|
|
@@ -841,10 +862,16 @@ ASSINAFY_API_KEY=... \
|
|
|
841
862
|
ASSINAFY_ACCOUNT_ID=... \
|
|
842
863
|
ASSINAFY_TEST_EMAIL=recipient1@example.com \
|
|
843
864
|
ASSINAFY_TEST_EMAIL2=recipient2@example.com \
|
|
865
|
+
ASSINAFY_TEMPLATE_ID=configured-template-id \
|
|
844
866
|
ASSINAFY_BASE_URL=https://sandbox.assinafy.com.br/v1 \
|
|
845
867
|
bundle exec rspec spec/integration
|
|
846
868
|
```
|
|
847
869
|
|
|
870
|
+
`ASSINAFY_TEMPLATE_ID` must identify a sandbox template with a `Signer` role configured in the
|
|
871
|
+
application. Without it, the positive template-generation example is skipped. Supply different test
|
|
872
|
+
recipients for its roles. OAuth browser consent is tested separately with a registered app, an HTTPS
|
|
873
|
+
callback, and the matching authorization server; sandbox keys are not production OAuth credentials.
|
|
874
|
+
|
|
848
875
|
> These tests create and clean up real resources and, for the assignment flow, send real signature-request emails to the addresses in `ASSINAFY_TEST_EMAIL` / `ASSINAFY_TEST_EMAIL2`.
|
|
849
876
|
|
|
850
877
|
## Contributing
|
data/README.md
CHANGED
|
@@ -83,6 +83,7 @@ A Assinafy aceita três credenciais. Escolha pela pergunta "**quem** está agind
|
|
|
83
83
|
|
|
84
84
|
```ruby
|
|
85
85
|
require 'assinafy'
|
|
86
|
+
require 'logger'
|
|
86
87
|
|
|
87
88
|
client = Assinafy::Client.new(
|
|
88
89
|
api_key: ENV.fetch('ASSINAFY_API_KEY'),
|
|
@@ -127,7 +128,7 @@ O caminho mais curto do PDF até o pedido de assinatura:
|
|
|
127
128
|
```ruby
|
|
128
129
|
resultado = client.upload_and_request_signatures(
|
|
129
130
|
source: './contrato.pdf',
|
|
130
|
-
signers: [{ full_name: 'Ana Silva', email: 'ana@
|
|
131
|
+
signers: [{ full_name: 'Ana Silva', email: 'ana@example.com' }],
|
|
131
132
|
message: 'Por favor, assine o contrato em anexo.'
|
|
132
133
|
)
|
|
133
134
|
|
|
@@ -192,18 +193,20 @@ Erros de rede durante a espera são tolerados e reprocessados; um status termina
|
|
|
192
193
|
```ruby
|
|
193
194
|
signatario = client.signers.create(
|
|
194
195
|
full_name: 'Ana Silva',
|
|
195
|
-
email: 'ana@
|
|
196
|
-
whatsapp_phone_number: '+
|
|
197
|
-
government_id: '00000000000' # obrigatório para certificado digital
|
|
196
|
+
email: 'ana@example.com',
|
|
197
|
+
whatsapp_phone_number: '+5500000000000' # substitua por um número de teste válido
|
|
198
198
|
)
|
|
199
199
|
signatario['id'] # => "19e6b92e7895332ed9708535d8c"
|
|
200
|
+
|
|
201
|
+
# Para certificado digital, cadastre o CPF/CNPJ real autorizado antes do assignment:
|
|
202
|
+
client.signers.update(signatario['id'], government_id: ENV.fetch('ASSINAFY_SIGNER_GOVERNMENT_ID'))
|
|
200
203
|
```
|
|
201
204
|
|
|
202
205
|
Signatários pertencem à conta e podem ser reaproveitados entre documentos:
|
|
203
206
|
|
|
204
207
|
```ruby
|
|
205
|
-
existente = client.signers.find_by_email('ana@
|
|
206
|
-
signatario = existente || client.signers.create(full_name: 'Ana Silva', email: 'ana@
|
|
208
|
+
existente = client.signers.find_by_email('ana@example.com') # paginação percorrida pelo SDK
|
|
209
|
+
signatario = existente || client.signers.create(full_name: 'Ana Silva', email: 'ana@example.com')
|
|
207
210
|
```
|
|
208
211
|
|
|
209
212
|
### 4.4 Estimar o custo (opcional, recomendado)
|
|
@@ -233,7 +236,7 @@ assignment = client.assignments.create(
|
|
|
233
236
|
{ id: signatario['id'], verification_method: 'Email', notification_methods: ['Email'], step: 1 }
|
|
234
237
|
],
|
|
235
238
|
message: 'Por favor, assine o contrato em anexo.',
|
|
236
|
-
expires_at: '
|
|
239
|
+
expires_at: '2099-12-31T23:59:00Z',
|
|
237
240
|
copy_receivers: [] # IDs que só recebem cópia
|
|
238
241
|
)
|
|
239
242
|
|
|
@@ -241,8 +244,9 @@ assignment['signing_urls']
|
|
|
241
244
|
# => [{ 'signer_id' => '19e6b...', 'url' => 'https://.../sign/...' }]
|
|
242
245
|
```
|
|
243
246
|
|
|
244
|
-
`step` define a ordem: todos do passo 1 assinam antes do passo 2.
|
|
245
|
-
|
|
247
|
+
`step` define a ordem: todos do passo 1 assinam antes do passo 2. Se informado para um
|
|
248
|
+
signatário, informe para todos, em uma sequência contínua iniciada em 1. Omita para assinatura
|
|
249
|
+
simultânea. `expires_at` deve ter fuso horário e estar pelo menos uma hora no futuro.
|
|
246
250
|
|
|
247
251
|
**`collect`** — campos posicionados página a página.
|
|
248
252
|
|
|
@@ -349,7 +353,7 @@ client.assignments.resend_notification(documento['id'], assignment['id'], signat
|
|
|
349
353
|
Estender o prazo:
|
|
350
354
|
|
|
351
355
|
```ruby
|
|
352
|
-
client.assignments.reset_expiration(documento['id'], assignment['id'], '
|
|
356
|
+
client.assignments.reset_expiration(documento['id'], assignment['id'], '2099-01-31T23:59:00Z')
|
|
353
357
|
```
|
|
354
358
|
|
|
355
359
|
Para acompanhar sem polling, use [webhooks](#8-webhooks).
|
|
@@ -417,7 +421,7 @@ session[:assinafy_issuer] = Assinafy::OAuth::AUTHORIZATION_SERVER # emiss
|
|
|
417
421
|
|
|
418
422
|
redirect_to Assinafy::OAuth.authorization_url(
|
|
419
423
|
client_id: ENV.fetch('ASSINAFY_CLIENT_ID'),
|
|
420
|
-
redirect_uri: 'https://app.
|
|
424
|
+
redirect_uri: 'https://app.example.com/oauth/callback',
|
|
421
425
|
code_verifier: verificador,
|
|
422
426
|
scope: %w[documents:read documents:write offline_access],
|
|
423
427
|
state: state
|
|
@@ -442,7 +446,7 @@ tokens = Assinafy::Client.new.oauth.exchange_code(
|
|
|
442
446
|
code: params.fetch(:code),
|
|
443
447
|
client_id: ENV.fetch('ASSINAFY_CLIENT_ID'),
|
|
444
448
|
code_verifier: session.delete(:assinafy_code_verifier),
|
|
445
|
-
redirect_uri: 'https://app.
|
|
449
|
+
redirect_uri: 'https://app.example.com/oauth/callback'
|
|
446
450
|
)
|
|
447
451
|
|
|
448
452
|
tokens['access_token'] # => "..."
|
|
@@ -533,6 +537,13 @@ servidor['token_endpoint'] # => "https://api.assinafy.com.br/
|
|
|
533
537
|
servidor['code_challenge_methods_supported'] # => ["S256"]
|
|
534
538
|
```
|
|
535
539
|
|
|
540
|
+
### Clientes internos de serviço
|
|
541
|
+
|
|
542
|
+
O servidor também publica o grant RFC 8693 `urn:ietf:params:oauth:grant-type:token-exchange`.
|
|
543
|
+
Ele é reservado a clientes confidenciais internos provisionados pela Assinafy; aplicativos de
|
|
544
|
+
marketplace usam autorização com PKCE e renovação. O método `client.oauth.token` aceita esse grant,
|
|
545
|
+
com `client_secret`, `subject_token`, `subject_token_type` e `resource`. Ele não emite refresh token.
|
|
546
|
+
|
|
536
547
|
### Erros OAuth
|
|
537
548
|
|
|
538
549
|
Os endpoints OAuth respondem com o objeto plano da RFC 6749, não com o envelope da API. O SDK
|
|
@@ -572,7 +583,7 @@ Os valores aceitos estão publicados em
|
|
|
572
583
|
valida localmente: um valor fora do enum levanta `ValidationError` **antes** de a requisição
|
|
573
584
|
sair — e portanto antes de qualquer signatário ser criado para aquele assignment.
|
|
574
585
|
|
|
575
|
-
Combinações permitidas: `Email` → notifica por `Email`; `Whatsapp` → notifica por `Whatsapp`;
|
|
586
|
+
Informe exatamente um canal em `notification_methods`. Combinações permitidas: `Email` → notifica por `Email`; `Whatsapp` → notifica por `Whatsapp`;
|
|
576
587
|
`DigitalCertificate` → notifica por `Email` **ou** `Whatsapp`.
|
|
577
588
|
|
|
578
589
|
### Certificado digital ICP-Brasil (A1/A3)
|
|
@@ -590,7 +601,7 @@ client.assignments.create(
|
|
|
590
601
|
)
|
|
591
602
|
```
|
|
592
603
|
|
|
593
|
-
Antes de
|
|
604
|
+
Antes de concluir a assinatura, o signatário precisa confirmar os dados de identidade e aceitar os
|
|
594
605
|
termos. O endpoint comum de assinatura **rejeita** signatários por certificado — a assinatura
|
|
595
606
|
deles é produzida por um handshake de dois passos com a extensão Web PKI:
|
|
596
607
|
|
|
@@ -603,7 +614,8 @@ POST /v1/signers/certificate/complete → data.signerName
|
|
|
603
614
|
> Essas duas rotas são extensões que **não constam do documento OpenAPI publicado**: a
|
|
604
615
|
> autenticação e os esquemas de requisição/resposta delas não são documentados. Como envolveria
|
|
605
616
|
> adivinhar o payload, o SDK **não** expõe essas duas chamadas — fale com a Assinafy antes de
|
|
606
|
-
>
|
|
617
|
+
> integrar essas rotas diretamente. Para A1/A3, direcione o signatário ao fluxo hospedado
|
|
618
|
+
> de assinatura da Assinafy usando `assignment['signing_urls']`.
|
|
607
619
|
|
|
608
620
|
Concluído o fluxo, baixar o artefato `pades` devolve a assinatura PAdES qualificada.
|
|
609
621
|
|
|
@@ -624,15 +636,19 @@ client.templates.download_page(template['id'], pagina_id)
|
|
|
624
636
|
client.templates.delete(template['id'])
|
|
625
637
|
```
|
|
626
638
|
|
|
627
|
-
|
|
639
|
+
Configure os papéis e campos no aplicativo Assinafy antes de gerar documentos. O upload cria
|
|
640
|
+
um papel `Editor`; a geração exige pelo menos um papel `Signer`. Use um signatário já existente
|
|
641
|
+
e diferente para cada papel. `role_id` vem de `template['roles']`, e `id` é o ID do signatário.
|
|
642
|
+
|
|
643
|
+
Gerar um documento a partir de um template com um único papel `Signer`:
|
|
628
644
|
|
|
629
645
|
```ruby
|
|
630
646
|
client.documents.estimate_cost_from_template(
|
|
631
|
-
template['id'], [{
|
|
647
|
+
template['id'], [{ role_id: 'papel-assinante-id', id: signatario['id'] }]
|
|
632
648
|
)
|
|
633
649
|
|
|
634
650
|
documento = client.documents.create_from_template(
|
|
635
|
-
template['id'], [{
|
|
651
|
+
template['id'], [{ role_id: 'papel-assinante-id', id: signatario['id'] }]
|
|
636
652
|
)
|
|
637
653
|
```
|
|
638
654
|
|
|
@@ -641,7 +657,7 @@ documento = client.documents.create_from_template(
|
|
|
641
657
|
Definições de campo reutilizáveis, com validação opcional por regex:
|
|
642
658
|
|
|
643
659
|
```ruby
|
|
644
|
-
campo = client.fields.create(name: 'CPF', type: 'text', regex: '
|
|
660
|
+
campo = client.fields.create(name: 'CPF', type: 'text', regex: '/^\d{11}$/')
|
|
645
661
|
client.fields.types # tipos de campo disponíveis
|
|
646
662
|
client.fields.list
|
|
647
663
|
client.fields.validate(campo['id'], '12345678901')
|
|
@@ -673,9 +689,9 @@ Registrar a assinatura de eventos da conta:
|
|
|
673
689
|
client.webhooks.list_event_types # eventos disponíveis
|
|
674
690
|
|
|
675
691
|
client.webhooks.register(
|
|
676
|
-
url: 'https://app.
|
|
677
|
-
email: 'ops@
|
|
678
|
-
events: %w[
|
|
692
|
+
url: 'https://app.example.com/webhooks/assinafy',
|
|
693
|
+
email: 'ops@example.com', # para avisos de falha de entrega
|
|
694
|
+
events: %w[document_ready document_prepared signer_signed_document],
|
|
679
695
|
is_active: true
|
|
680
696
|
)
|
|
681
697
|
|
|
@@ -685,20 +701,22 @@ client.webhooks.list_dispatches
|
|
|
685
701
|
client.webhooks.retry_dispatch(dispatch_id)
|
|
686
702
|
```
|
|
687
703
|
|
|
688
|
-
|
|
704
|
+
A API não publica um esquema de assinatura para os webhooks enviados. O verificador HMAC-SHA256
|
|
705
|
+
local é opcional: use-o quando um gateway seu assina o corpo bruto com um segredo compartilhado.
|
|
706
|
+
O cabeçalho abaixo é inserido por esse gateway, e não pela Assinafy:
|
|
689
707
|
|
|
690
708
|
```ruby
|
|
691
709
|
verificador = client.webhook_verifier # usa o webhook_secret do cliente
|
|
692
710
|
|
|
693
711
|
post '/webhooks/assinafy' do
|
|
694
712
|
corpo = request.body.read
|
|
695
|
-
assinatura = request.env['
|
|
713
|
+
assinatura = request.env['HTTP_X_WEBHOOK_SIGNATURE']
|
|
696
714
|
|
|
697
715
|
halt 401 unless verificador.verify(corpo, assinatura)
|
|
698
716
|
|
|
699
717
|
evento = verificador.extract_event(corpo)
|
|
700
718
|
case verificador.event_type(evento)
|
|
701
|
-
when '
|
|
719
|
+
when 'signer_signed_document' then processar(verificador.event_object(evento))
|
|
702
720
|
end
|
|
703
721
|
|
|
704
722
|
200
|
|
@@ -853,6 +871,20 @@ método novo chega à gem sem uma.
|
|
|
853
871
|
- [CHANGELOG.md](CHANGELOG.md) — histórico de versões
|
|
854
872
|
- [Documentação da API](https://api.assinafy.com.br/v1/docs)
|
|
855
873
|
|
|
874
|
+
## Desenvolvimento e testes
|
|
875
|
+
|
|
876
|
+
```bash
|
|
877
|
+
bundle exec rake spec
|
|
878
|
+
bundle exec rubocop
|
|
879
|
+
bundle exec bundler-audit check --update
|
|
880
|
+
rbs -I sig validate
|
|
881
|
+
ruby scripts/check_api_contract.rb
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
A [suíte sandbox](spec/integration/live_sandbox_spec.rb) fica fora da execução padrão.
|
|
885
|
+
Veja os [pré-requisitos e variáveis](README.en.md#live-integration-tests), incluindo um template
|
|
886
|
+
com papel `Signer` para testar a geração. Os testes enviam e-mails e removem os recursos criados.
|
|
887
|
+
|
|
856
888
|
## Licença
|
|
857
889
|
|
|
858
890
|
Distribuído sob a licença [MIT](LICENSE).
|