flashpi 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,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flashpi
4
+ module Resources
5
+ # Issues one document type. The answer is the document: `processing` right away (202), or,
6
+ # with `options: { waitForAuthorization: true }`, its outcome once the SRI decides (200),
7
+ # unless the wait ends first (202 with `processing` or `pending_sri`; follow it with webhooks
8
+ # or `documents.get`).
9
+ class DocumentIssuer < Internal::Resource
10
+ # Seconds the API waits for the SRI in sync mode when `waitTimeoutSeconds` is not sent.
11
+ DEFAULT_WAIT_SECONDS = 15
12
+ # Time on top of the sync wait for Flashpi to build, sign and answer.
13
+ SYNC_MARGIN_SECONDS = 15
14
+
15
+ response_methods :create
16
+
17
+ # The shortest per-attempt timeout for issuing `body`, in seconds.
18
+ #
19
+ # In sync mode (`options.waitForAuthorization`) the API holds the request for up to
20
+ # `waitTimeoutSeconds` (15 by default) while the SRI decides, so each attempt waits at least
21
+ # that plus 15 seconds. Otherwise 0: the client's timeout applies.
22
+ #
23
+ # @return [Float]
24
+ def self.sync_timeout_floor(body)
25
+ settings = member(body, :options)
26
+ return 0.0 unless member(settings, :waitForAuthorization) == true
27
+
28
+ wait = member(settings, :waitTimeoutSeconds)
29
+ wait = DEFAULT_WAIT_SECONDS unless wait.is_a?(Integer) || (wait.is_a?(Float) && wait.finite?)
30
+ (wait + SYNC_MARGIN_SECONDS).to_f
31
+ end
32
+
33
+ # @api private
34
+ def self.member(value, name)
35
+ return nil unless value.is_a?(Hash)
36
+
37
+ value.key?(name) ? value[name] : value[name.to_s]
38
+ end
39
+
40
+ # Issues the document. Retries reuse the same Idempotency-Key, and when a retry finds the
41
+ # document already created by an earlier attempt, its current state is returned instead of
42
+ # an error.
43
+ #
44
+ # @param body [Hash] the document, with the API's camelCase keys
45
+ # @return [Hash{Symbol => Object}] the document
46
+ def create(body, **options)
47
+ floor = DocumentIssuer.sync_timeout_floor(body)
48
+ send_request(operation(:post, self.class::PATH, body:, options:, min_timeout: floor, recover_document: true))
49
+ end
50
+ end
51
+
52
+ # Invoices (facturas).
53
+ class Invoices < DocumentIssuer
54
+ PATH = "/v1/invoices"
55
+ end
56
+
57
+ # Credit notes (notas de crédito).
58
+ class CreditNotes < DocumentIssuer
59
+ PATH = "/v1/credit-notes"
60
+ end
61
+
62
+ # Debit notes (notas de débito).
63
+ class DebitNotes < DocumentIssuer
64
+ PATH = "/v1/debit-notes"
65
+ end
66
+
67
+ # Purchase settlements (liquidaciones de compra).
68
+ class PurchaseSettlements < DocumentIssuer
69
+ PATH = "/v1/purchase-settlements"
70
+ end
71
+
72
+ # Waybills (guías de remisión).
73
+ class Waybills < DocumentIssuer
74
+ PATH = "/v1/waybills"
75
+ end
76
+
77
+ # Withholding receipts (comprobantes de retención).
78
+ class Withholdings < DocumentIssuer
79
+ PATH = "/v1/withholdings"
80
+ end
81
+
82
+ # Every issued document, of any type: search, XML, RIDE (PDF) and email.
83
+ class Documents < Internal::Resource
84
+ XML_ACCEPT = "application/xml, application/problem+json"
85
+ PDF_ACCEPT = "application/pdf, application/problem+json"
86
+ XML_KINDS = %w[authorized signed unsigned].freeze
87
+
88
+ response_methods :list, :get, :xml, :pdf, :send_email
89
+
90
+ # Lists documents, newest first, with optional filters.
91
+ #
92
+ # @param params [Hash, nil] filters with the API's names (`status`, `issueDateFrom`, ...)
93
+ # @return [Flashpi::Page]
94
+ def list(params = nil, **options)
95
+ paginate(operation(:get, "/v1/documents", query: params, options:))
96
+ end
97
+
98
+ # Retrieves a document with its tax status, SRI messages, RIDE and email state.
99
+ #
100
+ # @return [Hash{Symbol => Object}]
101
+ def get(document_id, **options)
102
+ send_request(operation(:get, "/v1/documents/{}", document_id, options:))
103
+ end
104
+
105
+ # The document's XML: the authorized one once the SRI authorizes it, otherwise the signed or
106
+ # unsigned one (see `kind`).
107
+ #
108
+ # @return [Hash{Symbol => Object}] `{ xml: String, kind: "authorized" | "signed" | "unsigned" | nil }`
109
+ def xml(document_id, **options)
110
+ result = send_call(
111
+ operation(:get, "/v1/documents/{}/xml", document_id, accept: XML_ACCEPT, response_type: :text, options:)
112
+ )
113
+ kind = result.headers["flashpi-xml-kind"]
114
+ _ = { xml: result.data, kind: XML_KINDS.include?(kind) ? kind : nil }
115
+ end
116
+
117
+ # The RIDE (the printable PDF) of an authorized document, as a binary String. Raises a
118
+ # ConflictError with code `ride_not_available` while it is still being generated.
119
+ #
120
+ # @return [String]
121
+ def pdf(document_id, **options)
122
+ send_request(
123
+ operation(:get, "/v1/documents/{}/pdf", document_id, accept: PDF_ACCEPT, response_type: :bytes, options:)
124
+ )
125
+ end
126
+
127
+ # Emails the authorized document (RIDE and XML) again to the address of its request. The new
128
+ # delivery shows in `email.deliveries`; daily limits apply (RateLimitError).
129
+ #
130
+ # @return [Hash{Symbol => Object}] the document
131
+ def send_email(document_id, **options)
132
+ send_request(operation(:post, "/v1/documents/{}/email", document_id, body: {}, options:))
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flashpi
4
+ module Resources
5
+ # Events: what happened to documents, certificates and email sending, newest first.
6
+ class Events < Internal::Resource
7
+ response_methods :list, :get
8
+
9
+ # Lists events, optionally by type, issuer, document or certificate.
10
+ #
11
+ # @return [Flashpi::Page]
12
+ def list(params = nil, **options)
13
+ paginate(operation(:get, "/v1/events", query: params, options:))
14
+ end
15
+
16
+ # Retrieves an event: the same object its webhook delivered.
17
+ #
18
+ # @return [Hash{Symbol => Object}] the event
19
+ def get(event_id, **options)
20
+ send_request(operation(:get, "/v1/events/{}", event_id, options:))
21
+ end
22
+ end
23
+
24
+ # The API's own status. It needs no API key.
25
+ class Status < Internal::Resource
26
+ response_methods :get
27
+
28
+ # Whether Flashpi is operational.
29
+ #
30
+ # @return [Hash{Symbol => Object}]
31
+ def get(**options)
32
+ send_request(operation(:get, "/v1/status", options:))
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flashpi
4
+ module Resources
5
+ # Creates an issuer from its .p12: upload the certificate, wait while the onboarding is
6
+ # `verifying` until it is `ready` (Flashpi reads the holder, RUC and SRI data) or `rejected`,
7
+ # review the prefill, then complete it.
8
+ #
9
+ # Needs an account-scoped API key with read_write access.
10
+ class IssuerOnboardings < Internal::Resource
11
+ response_methods :create, :get, :complete
12
+
13
+ # Starts an onboarding with a .p12 and its password. It starts `verifying`.
14
+ #
15
+ # @param upload [Hash] `{ file: String or IO, password: String, filename: String }`
16
+ # @return [Hash{Symbol => Object}] the onboarding
17
+ def create(upload, **options)
18
+ form = Internal::Multipart.certificate(upload)
19
+ send_request(operation(:post, "/v1/issuer-onboardings", multipart: form, options:))
20
+ end
21
+
22
+ # Retrieves an onboarding, to follow it while it is `verifying`.
23
+ #
24
+ # @return [Hash{Symbol => Object}] the onboarding
25
+ def get(onboarding_id, **options)
26
+ send_request(operation(:get, "/v1/issuer-onboardings/{}", onboarding_id, options:))
27
+ end
28
+
29
+ # Completes a `ready` onboarding.
30
+ #
31
+ # - For a new RUC, `body` is the issuer to create (as in `issuers.create`): `legalName`,
32
+ # `headOfficeAddress` and `taxProfile` are required. Build it from `onboarding[:prefill]`;
33
+ # the API does not apply the prefill by itself.
34
+ # - When `onboarding[:existingIssuer]` is set, `body` can be left out (`{}` is sent): the
35
+ # certificate is added to that issuer for both modes (a renewal) and the issuer is left as
36
+ # it is.
37
+ # - Send `ruc` when `onboarding[:rucNeedsConfirmation]` is true.
38
+ #
39
+ # @return [Hash{Symbol => Object}] the completed onboarding, with its issuer
40
+ def complete(onboarding_id, body = nil, **options)
41
+ send_request(
42
+ operation(:post, "/v1/issuer-onboardings/{}/complete", onboarding_id, body: body.nil? ? {} : body, options:)
43
+ )
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flashpi
4
+ module Resources
5
+ # Emission points (the second 3-digit series code) of an establishment.
6
+ class EmissionPoints < Internal::Resource
7
+ PATH = "/v1/issuers/{}/establishments/{}/emission-points"
8
+ ITEM = "/v1/issuers/{}/establishments/{}/emission-points/{}"
9
+
10
+ response_methods :create, :list, :get, :delete
11
+
12
+ # @return [Hash{Symbol => Object}] the emission point
13
+ def create(issuer_id, establishment_id, body, **options)
14
+ send_request(operation(:post, PATH, issuer_id, establishment_id, body:, options:))
15
+ end
16
+
17
+ # Lists the emission points of an establishment, by code.
18
+ #
19
+ # @return [Flashpi::Page]
20
+ def list(issuer_id, establishment_id, params = nil, **options)
21
+ paginate(operation(:get, PATH, issuer_id, establishment_id, query: params, options:))
22
+ end
23
+
24
+ # @return [Hash{Symbol => Object}] the emission point
25
+ def get(issuer_id, establishment_id, emission_point_id, **options)
26
+ send_request(operation(:get, ITEM, issuer_id, establishment_id, emission_point_id, options:))
27
+ end
28
+
29
+ # Deletes an emission point that has no documents.
30
+ #
31
+ # @return [nil]
32
+ def delete(issuer_id, establishment_id, emission_point_id, **options)
33
+ send_request(
34
+ operation(:delete, ITEM, issuer_id, establishment_id, emission_point_id, response_type: :none, options:)
35
+ )
36
+ end
37
+ end
38
+
39
+ # Establishments (the 3-digit series codes such as `001`) of an issuer.
40
+ class Establishments < Internal::Resource
41
+ PATH = "/v1/issuers/{}/establishments"
42
+ ITEM = "/v1/issuers/{}/establishments/{}"
43
+
44
+ response_methods :create, :list, :get, :update, :delete
45
+
46
+ # @return [EmissionPoints] the emission points of an establishment
47
+ attr_reader :emission_points
48
+
49
+ def initialize(engine)
50
+ super
51
+ @emission_points = EmissionPoints.new(engine)
52
+ end
53
+
54
+ # @return [Hash{Symbol => Object}] the establishment
55
+ def create(issuer_id, body, **options)
56
+ send_request(operation(:post, PATH, issuer_id, body:, options:))
57
+ end
58
+
59
+ # Lists the establishments of an issuer, by code.
60
+ #
61
+ # @return [Flashpi::Page]
62
+ def list(issuer_id, params = nil, **options)
63
+ paginate(operation(:get, PATH, issuer_id, query: params, options:))
64
+ end
65
+
66
+ # @return [Hash{Symbol => Object}] the establishment
67
+ def get(issuer_id, establishment_id, **options)
68
+ send_request(operation(:get, ITEM, issuer_id, establishment_id, options:))
69
+ end
70
+
71
+ # Updates an establishment (only the fields sent).
72
+ #
73
+ # @return [Hash{Symbol => Object}] the establishment
74
+ def update(issuer_id, establishment_id, body, **options)
75
+ send_request(operation(:patch, ITEM, issuer_id, establishment_id, body:, options:))
76
+ end
77
+
78
+ # Deletes an establishment that has no documents, with its emission points.
79
+ #
80
+ # @return [nil]
81
+ def delete(issuer_id, establishment_id, **options)
82
+ send_request(operation(:delete, ITEM, issuer_id, establishment_id, response_type: :none, options:))
83
+ end
84
+ end
85
+
86
+ # The .p12 certificates (firmas electrónicas) of an issuer.
87
+ class Certificates < Internal::Resource
88
+ PATH = "/v1/issuers/{}/certificates"
89
+ ITEM = "/v1/issuers/{}/certificates/{}"
90
+
91
+ response_methods :upload, :list, :get
92
+
93
+ # Uploads a .p12 for the issuer, for the mode of the API key only (test or live). To renew
94
+ # the certificate of both modes, start an `issuer_onboardings.create` with it instead.
95
+ #
96
+ # Flashpi checks it in the background: the certificate starts `processing`, then becomes
97
+ # `accepted` (event `certificate.verified`, or `certificate.pending` while its revocation
98
+ # status cannot be checked) or `rejected` (no event: follow it with `get` and read
99
+ # `rejection`).
100
+ #
101
+ # @param upload [Hash] `{ file: String or IO, password: String, filename: String }`
102
+ # @return [Hash{Symbol => Object}] the certificate
103
+ def upload(issuer_id, upload, **options)
104
+ form = Internal::Multipart.certificate(upload)
105
+ send_request(operation(:post, PATH, issuer_id, multipart: form, options:))
106
+ end
107
+
108
+ # Lists the certificates of an issuer, newest first.
109
+ #
110
+ # @return [Flashpi::Page]
111
+ def list(issuer_id, params = nil, **options)
112
+ paginate(operation(:get, PATH, issuer_id, query: params, options:))
113
+ end
114
+
115
+ # @return [Hash{Symbol => Object}] the certificate
116
+ def get(issuer_id, certificate_id, **options)
117
+ send_request(operation(:get, ITEM, issuer_id, certificate_id, options:))
118
+ end
119
+ end
120
+
121
+ # The issuers (RUCs) of the account, with their establishments, emission points and .p12
122
+ # certificates. Needs an account-scoped API key with read_write access, also to read.
123
+ class Issuers < Internal::Resource
124
+ PATH = "/v1/issuers"
125
+ ITEM = "/v1/issuers/{}"
126
+
127
+ response_methods :create, :list, :get, :update, :delete
128
+
129
+ # @return [Establishments] establishments of an issuer, with their emission points
130
+ attr_reader :establishments
131
+ # @return [Certificates] the .p12 certificates of an issuer
132
+ attr_reader :certificates
133
+
134
+ def initialize(engine)
135
+ super
136
+ @establishments = Establishments.new(engine)
137
+ @certificates = Certificates.new(engine)
138
+ end
139
+
140
+ # Creates an issuer. To start from its .p12 instead, see `issuer_onboardings`.
141
+ #
142
+ # @return [Hash{Symbol => Object}] the issuer
143
+ def create(body, **options)
144
+ send_request(operation(:post, PATH, body:, options:))
145
+ end
146
+
147
+ # Lists the issuers, newest first.
148
+ #
149
+ # @return [Flashpi::Page]
150
+ def list(params = nil, **options)
151
+ paginate(operation(:get, PATH, query: params, options:))
152
+ end
153
+
154
+ # Retrieves an issuer.
155
+ #
156
+ # @return [Hash{Symbol => Object}] the issuer
157
+ def get(issuer_id, **options)
158
+ send_request(operation(:get, ITEM, issuer_id, options:))
159
+ end
160
+
161
+ # Updates an issuer's name, tax profile, branding or defaults (only the fields sent).
162
+ #
163
+ # @return [Hash{Symbol => Object}] the issuer
164
+ def update(issuer_id, body, **options)
165
+ send_request(operation(:patch, ITEM, issuer_id, body:, options:))
166
+ end
167
+
168
+ # Deletes an issuer that has no documents and no certificates, with its establishments,
169
+ # emission points and issuer-scoped API keys. Otherwise 409 `resource_in_use`: certificates
170
+ # cannot be deleted, so an issuer that ever had a .p12 (one from `issuer_onboardings` too)
171
+ # stays.
172
+ #
173
+ # @return [nil]
174
+ def delete(issuer_id, **options)
175
+ send_request(operation(:delete, ITEM, issuer_id, response_type: :none, options:))
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flashpi
4
+ module Resources
5
+ # Deliveries of events to one endpoint.
6
+ class WebhookDeliveries < Internal::Resource
7
+ PATH = "/v1/webhook-endpoints/{}/deliveries"
8
+ ITEM = "/v1/webhook-endpoints/{}/deliveries/{}"
9
+
10
+ response_methods :list, :get, :replay
11
+
12
+ # Lists deliveries, newest first, optionally by status, event type or event.
13
+ #
14
+ # @return [Flashpi::Page]
15
+ def list(endpoint_id, params = nil, **options)
16
+ paginate(operation(:get, PATH, endpoint_id, query: params, options:))
17
+ end
18
+
19
+ # Retrieves a delivery with its attempts.
20
+ #
21
+ # @return [Hash{Symbol => Object}] the delivery
22
+ def get(endpoint_id, delivery_id, **options)
23
+ send_request(operation(:get, ITEM, endpoint_id, delivery_id, options:))
24
+ end
25
+
26
+ # Sends the delivery's event again, with the same `webhook-id`.
27
+ #
28
+ # @return [Hash{Symbol => Object}] the delivery
29
+ def replay(endpoint_id, delivery_id, **options)
30
+ send_request(operation(:post, "#{ITEM}/replay", endpoint_id, delivery_id, options:))
31
+ end
32
+ end
33
+
34
+ # The URLs Flashpi sends events to. Needs an account-scoped API key with read_write access.
35
+ # To check the signature of a webhook you receive, use {Flashpi::Webhooks} instead.
36
+ class WebhookEndpoints < Internal::Resource
37
+ PATH = "/v1/webhook-endpoints"
38
+ ITEM = "/v1/webhook-endpoints/{}"
39
+
40
+ response_methods :create, :list, :get, :update, :delete, :get_secret, :rotate_secret, :verify
41
+
42
+ # @return [WebhookDeliveries] the deliveries of an endpoint: attempts, answers and replays
43
+ attr_reader :deliveries
44
+
45
+ def initialize(engine)
46
+ super
47
+ @deliveries = WebhookDeliveries.new(engine)
48
+ end
49
+
50
+ # Creates an endpoint. It stays `pending_verification` until it answers the activation
51
+ # challenge; read its secret with {#get_secret} first, then call {#verify} if needed.
52
+ #
53
+ # @return [Hash{Symbol => Object}] the endpoint
54
+ def create(body, **options)
55
+ send_request(operation(:post, PATH, body:, options:))
56
+ end
57
+
58
+ # Lists the endpoints, newest first.
59
+ #
60
+ # @return [Flashpi::Page]
61
+ def list(params = nil, **options)
62
+ paginate(operation(:get, PATH, query: params, options:))
63
+ end
64
+
65
+ # Retrieves an endpoint.
66
+ #
67
+ # @return [Hash{Symbol => Object}] the endpoint
68
+ def get(endpoint_id, **options)
69
+ send_request(operation(:get, ITEM, endpoint_id, options:))
70
+ end
71
+
72
+ # Updates the URL, events, payload mode, description or `disabled` (only the fields sent).
73
+ #
74
+ # @return [Hash{Symbol => Object}] the endpoint
75
+ def update(endpoint_id, body, **options)
76
+ send_request(operation(:patch, ITEM, endpoint_id, body:, options:))
77
+ end
78
+
79
+ # Deletes an endpoint.
80
+ #
81
+ # @return [nil]
82
+ def delete(endpoint_id, **options)
83
+ send_request(operation(:delete, ITEM, endpoint_id, response_type: :none, options:))
84
+ end
85
+
86
+ # The signing secret (`whsec_...`) and, during a rotation, when the previous one expires.
87
+ #
88
+ # @return [Hash{Symbol => Object}]
89
+ def get_secret(endpoint_id, **options)
90
+ send_request(operation(:get, "#{ITEM}/secret", endpoint_id, options:))
91
+ end
92
+
93
+ # Replaces the signing secret. The previous one keeps signing for
94
+ # `previousSecretExpiresInHours` (24 by default, 0 to drop it at once). Read the new secret
95
+ # with {#get_secret}.
96
+ #
97
+ # @return [Hash{Symbol => Object}] the endpoint
98
+ def rotate_secret(endpoint_id, body = nil, **options)
99
+ send_request(operation(:post, "#{ITEM}/rotate-secret", endpoint_id, body:, options:))
100
+ end
101
+
102
+ # Sends the activation challenge (`webhook_endpoint.verification`) to the endpoint again.
103
+ #
104
+ # @return [Hash{Symbol => Object}] the endpoint
105
+ def verify(endpoint_id, **options)
106
+ send_request(operation(:post, "#{ITEM}/verify", endpoint_id, options:))
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flashpi
4
+ # The version of this gem, updated by release-please on every release.
5
+ VERSION = "0.1.0"
6
+ end