fiscalrail 0.5.0 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d9fcbcf8bfd05d48cce8ca79c50e6053d989ef54c0a2b67b5c386764f71b8a0
4
- data.tar.gz: e1026a8001318bfb1914add9feb13409818b1f8d54937fc7569c4776474763a9
3
+ metadata.gz: 686707319e3f84edc7570df96ecde4325051ced614c1d7afb0706fb490730fbe
4
+ data.tar.gz: 9b72d4865f113b5d9e1d18668a390ec8d6900b1ab05d06ca9ef12cf933a47c90
5
5
  SHA512:
6
- metadata.gz: 85026a467a16b3bc54a33306720743d41aba4ea63b7e3072e30240f2b4d67bb6d54cf38293d92098b9ad0b05da755e671c39e6e559bd0675c942a5fe78c05995
7
- data.tar.gz: ef408ec27b12fe12d907de2abaa8d15a9be13485437f1c5985f442d94fde4615dc519975805221c266acbfdb1834aaaa0c8ee8f39918db98c743bf3d9371f43a
6
+ metadata.gz: 7b72ea35beff00c975ccb4dcb1f4953b56805cd64b330dee8cd4d39086158043574a4f10e51d28eac3639bea26cde7dbd0bfdcf068cbf66a8bd4d1838e5b4836
7
+ data.tar.gz: aae2526ccbe70ba3872c6859861cb972f743e72b1fc653fd28006d8236b49275583f48651e034fae59f6dbd109e36e841fec16d842efb0d8770dfd88dc9e0d04
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0
4
+
5
+ - Add ES-scoped issuer certificate file uploads, representation verification,
6
+ submission-verification retries and pending-change cancellation.
7
+ - Expose active submission readiness and pending verification results from the
8
+ deployed API contract (47 operations).
9
+ - Keep certificate uploads as native multipart requests without automatic retries.
10
+
11
+
3
12
  ## 0.5.0 — 2026-09-28
4
13
 
5
14
  - Match the deployed current-account API routes and split invoicing settings into a dedicated resource.
data/README.md CHANGED
@@ -5,7 +5,7 @@ A Ruby client for issuing immutable invoices through FiscalRail. Ruby 3.3 or lat
5
5
  Install with Bundler:
6
6
 
7
7
  ```ruby
8
- gem "fiscalrail", "~> 0.5.0"
8
+ gem "fiscalrail", "~> 0.6.0"
9
9
  ```
10
10
 
11
11
  ## Issue an invoice
@@ -230,3 +230,32 @@ FISCALRAIL_APP_ROOT="$PWD" bundle exec ruby /absolute/path/to/ruby/test/rails_in
230
230
  It creates a Test account inside a test transaction and checks customer creation, null clearing, payment defaults, issuance/replay, pagination, PDF download and amendment. Set `FISCALRAIL_SDK_PDF=/tmp/sdk-invoice.pdf` to retain the rendered PDF for inspection. This test is optional and is not part of the standalone gem CI.
231
231
 
232
232
  See [RELEASING.md](RELEASING.md) for the release checklist.
233
+
234
+
235
+ ## Spanish AEAT submission
236
+
237
+ Use a Live Spanish account key. Upload a `.p12`/`.pfx` file (up to 128 KiB),
238
+ including its private key, using native multipart upload. Omit the password for
239
+ an unprotected bundle. The certificate's issuer NIF must match the account.
240
+
241
+ ```ruby
242
+ File.open("issuer.p12", "rb") do |certificate|
243
+ setup = client.account_tax_regimes.es.upload_certificate(
244
+ certificate_file: certificate,
245
+ certificate_password: ENV.fetch("CERTIFICATE_PASSWORD")
246
+ )
247
+ end
248
+ setup = client.account_tax_regimes.retrieve
249
+ # Inspect setup.es.pending_submission.status / error_code and setup.es.submission.ready.
250
+ client.account_tax_regimes.es.verify_submission # retry the pending or active check
251
+ client.account_tax_regimes.es.cancel_submission_change
252
+ client.account_tax_regimes.es.verify_representation # after granting AEAT authority
253
+ ```
254
+
255
+ Each mutation above is a separate operation; choose the one needed. Upload and
256
+ verification return the account setup while AEAT checks run asynchronously.
257
+ Poll the generic account tax-regime resource for pending verification status,
258
+ error code and the active setup's readiness. A working setup remains active until
259
+ a replacement verifies; failed checks retain the pending certificate for retry.
260
+ Cancelling removes only the pending change. Uploads are not automatically retried.
261
+ The ES mutations use `/account/tax-regime/es/...`; reads use `/account/tax-regime`.
@@ -2365,6 +2365,49 @@ module FiscalRail
2365
2365
  ],
2366
2366
  "type": "object"
2367
2367
  },
2368
+ "SpanishAccountSubmission": {
2369
+ "model": "SpanishAccountSubmission",
2370
+ "properties": {
2371
+ "certificate_expires_at": {
2372
+ "format": "date-time",
2373
+ "type": [
2374
+ "string",
2375
+ "null"
2376
+ ]
2377
+ },
2378
+ "error_code": {
2379
+ "type": [
2380
+ "string",
2381
+ "null"
2382
+ ]
2383
+ },
2384
+ "kind": {
2385
+ "type": "string"
2386
+ },
2387
+ "last_checked_at": {
2388
+ "format": "date-time",
2389
+ "type": [
2390
+ "string",
2391
+ "null"
2392
+ ]
2393
+ },
2394
+ "ready": {
2395
+ "type": "boolean"
2396
+ },
2397
+ "status": {
2398
+ "type": "string"
2399
+ }
2400
+ },
2401
+ "required": [
2402
+ "kind",
2403
+ "ready",
2404
+ "certificate_expires_at",
2405
+ "status",
2406
+ "error_code",
2407
+ "last_checked_at"
2408
+ ],
2409
+ "type": "object"
2410
+ },
2368
2411
  "SpanishAccountTaxRegime": {
2369
2412
  "model": "SpanishAccountTaxRegime",
2370
2413
  "properties": {
@@ -2394,6 +2437,16 @@ module FiscalRail
2394
2437
  "SpanishAccountTaxRegimeDetails": {
2395
2438
  "model": "SpanishAccountTaxRegimeDetails",
2396
2439
  "properties": {
2440
+ "pending_submission": {
2441
+ "oneOf": [
2442
+ {
2443
+ "$ref": "#/components/schemas/SpanishPendingSubmission"
2444
+ },
2445
+ {
2446
+ "type": "null"
2447
+ }
2448
+ ]
2449
+ },
2397
2450
  "representation": {
2398
2451
  "oneOf": [
2399
2452
  {
@@ -2403,10 +2456,22 @@ module FiscalRail
2403
2456
  "type": "null"
2404
2457
  }
2405
2458
  ]
2459
+ },
2460
+ "submission": {
2461
+ "oneOf": [
2462
+ {
2463
+ "$ref": "#/components/schemas/SpanishAccountSubmission"
2464
+ },
2465
+ {
2466
+ "type": "null"
2467
+ }
2468
+ ]
2406
2469
  }
2407
2470
  },
2408
2471
  "required": [
2409
- "representation"
2472
+ "representation",
2473
+ "submission",
2474
+ "pending_submission"
2410
2475
  ],
2411
2476
  "type": "object"
2412
2477
  },
@@ -2460,6 +2525,89 @@ module FiscalRail
2460
2525
  ],
2461
2526
  "type": "object"
2462
2527
  },
2528
+ "SpanishPendingSubmission": {
2529
+ "model": "SpanishPendingSubmission",
2530
+ "properties": {
2531
+ "certificate_expires_at": {
2532
+ "format": "date-time",
2533
+ "type": [
2534
+ "string",
2535
+ "null"
2536
+ ]
2537
+ },
2538
+ "error_code": {
2539
+ "type": [
2540
+ "string",
2541
+ "null"
2542
+ ]
2543
+ },
2544
+ "kind": {
2545
+ "type": "string"
2546
+ },
2547
+ "last_checked_at": {
2548
+ "format": "date-time",
2549
+ "type": [
2550
+ "string",
2551
+ "null"
2552
+ ]
2553
+ },
2554
+ "status": {
2555
+ "type": "string"
2556
+ }
2557
+ },
2558
+ "required": [
2559
+ "kind",
2560
+ "status",
2561
+ "error_code",
2562
+ "last_checked_at",
2563
+ "certificate_expires_at"
2564
+ ],
2565
+ "type": "object"
2566
+ },
2567
+ "SubmissionConfigurationErrorResponse": {
2568
+ "model": "SubmissionConfigurationErrorResponse",
2569
+ "properties": {
2570
+ "error": {
2571
+ "model": "SubmissionConfigurationError",
2572
+ "properties": {
2573
+ "code": {
2574
+ "type": "string"
2575
+ },
2576
+ "details": {
2577
+ "items": {
2578
+ "model": "SubmissionConfigurationValidationDetail",
2579
+ "properties": {
2580
+ "field": {
2581
+ "type": "string"
2582
+ },
2583
+ "message": {
2584
+ "type": "string"
2585
+ }
2586
+ },
2587
+ "required": [
2588
+ "field",
2589
+ "message"
2590
+ ],
2591
+ "type": "object"
2592
+ },
2593
+ "type": "array"
2594
+ },
2595
+ "message": {
2596
+ "type": "string"
2597
+ }
2598
+ },
2599
+ "required": [
2600
+ "code",
2601
+ "message"
2602
+ ],
2603
+ "type": "object"
2604
+ }
2605
+ },
2606
+ "required": [
2607
+ "error"
2608
+ ],
2609
+ "type": "object"
2610
+ },
2463
2611
  "TaxId": {
2464
2612
  "model": "TaxId",
2465
2613
  "properties": {
@@ -2914,6 +3062,16 @@ module FiscalRail
2914
3062
  }
2915
3063
  }
2916
3064
  },
3065
+ "cancelAccountSubmissionChange": {
3066
+ "method": "DELETE",
3067
+ "path": "/account/tax-regime/es/submission/pending",
3068
+ "parameters": [],
3069
+ "responses": {
3070
+ "200": {
3071
+ "$ref": "#/components/schemas/AccountTaxRegime"
3072
+ }
3073
+ }
3074
+ },
2917
3075
  "createApiKey": {
2918
3076
  "method": "POST",
2919
3077
  "path": "/api-keys",
@@ -3587,6 +3745,36 @@ module FiscalRail
3587
3745
  "$ref": "#/components/schemas/PaymentInstruction"
3588
3746
  }
3589
3747
  }
3748
+ },
3749
+ "uploadAccountCertificate": {
3750
+ "method": "POST",
3751
+ "path": "/account/tax-regime/es/certificate",
3752
+ "parameters": [],
3753
+ "responses": {
3754
+ "202": {
3755
+ "$ref": "#/components/schemas/AccountTaxRegime"
3756
+ }
3757
+ }
3758
+ },
3759
+ "verifyAccountRepresentation": {
3760
+ "method": "POST",
3761
+ "path": "/account/tax-regime/es/representation/verify",
3762
+ "parameters": [],
3763
+ "responses": {
3764
+ "202": {
3765
+ "$ref": "#/components/schemas/AccountTaxRegime"
3766
+ }
3767
+ }
3768
+ },
3769
+ "verifyAccountSubmission": {
3770
+ "method": "POST",
3771
+ "path": "/account/tax-regime/es/submission/verify",
3772
+ "parameters": [],
3773
+ "responses": {
3774
+ "202": {
3775
+ "$ref": "#/components/schemas/AccountTaxRegime"
3776
+ }
3777
+ }
3590
3778
  }
3591
3779
  }
3592
3780
  }
@@ -593,6 +593,15 @@ module FiscalRail
593
593
  def verified_at = self["verified_at"]
594
594
  end
595
595
 
596
+ class SpanishAccountSubmission < Model
597
+ def certificate_expires_at = self["certificate_expires_at"]
598
+ def error_code = self["error_code"]
599
+ def kind = self["kind"]
600
+ def last_checked_at = self["last_checked_at"]
601
+ def ready = self["ready"]
602
+ def status = self["status"]
603
+ end
604
+
596
605
  class SpanishAccountTaxRegime < Model
597
606
  def account = self["account"]
598
607
  def es = self["es"]
@@ -601,7 +610,9 @@ module FiscalRail
601
610
  end
602
611
 
603
612
  class SpanishAccountTaxRegimeDetails < Model
613
+ def pending_submission = self["pending_submission"]
604
614
  def representation = self["representation"]
615
+ def submission = self["submission"]
605
616
  end
606
617
 
607
618
  class SpanishInvoiceQr < Model
@@ -619,6 +630,29 @@ module FiscalRail
619
630
  def verifactu = self["verifactu"]
620
631
  end
621
632
 
633
+ class SpanishPendingSubmission < Model
634
+ def certificate_expires_at = self["certificate_expires_at"]
635
+ def error_code = self["error_code"]
636
+ def kind = self["kind"]
637
+ def last_checked_at = self["last_checked_at"]
638
+ def status = self["status"]
639
+ end
640
+
641
+ class SubmissionConfigurationError < Model
642
+ def code = self["code"]
643
+ def details = self["details"]
644
+ def message = self["message"]
645
+ end
646
+
647
+ class SubmissionConfigurationErrorResponse < Model
648
+ def error = self["error"]
649
+ end
650
+
651
+ class SubmissionConfigurationValidationDetail < Model
652
+ def field = self["field"]
653
+ def message = self["message"]
654
+ end
655
+
622
656
  class TaxId < Model
623
657
  def country = self["country"]
624
658
  def id = self["id"]
@@ -10,7 +10,7 @@ module FiscalRail
10
10
 
11
11
  private
12
12
 
13
- def request(operation_id, path: {}, params: {}, body: nil, retry_safe: false, idempotency_key: nil, headers: {}, binary: false)
13
+ def request(operation_id, path: {}, params: {}, body: nil, multipart: nil, retry_safe: false, idempotency_key: nil, headers: {}, binary: false)
14
14
  operation = Generated::OPERATIONS.fetch(operation_id)
15
15
  route = operation.fetch("path").gsub(/\{([^}]+)\}/) do
16
16
  value = path.fetch(Regexp.last_match(1).to_sym)
@@ -20,7 +20,7 @@ module FiscalRail
20
20
  end
21
21
  headers = headers.merge("Accept" => "application/pdf") if binary
22
22
  response = @transport.request(method: operation.fetch("method"), path: route, params: params,
23
- body: body, retry_safe: retry_safe, idempotency_key: idempotency_key, headers: headers)
23
+ body: body, multipart: multipart, retry_safe: retry_safe, idempotency_key: idempotency_key, headers: headers)
24
24
  metadata = { request_id: response.headers["request-id"], idempotent_replayed: response.headers["idempotent-replayed"], idempotency_key: idempotency_key }
25
25
  unless operation.fetch("responses").key?(response.status.to_s)
26
26
  raise ResponseParseError.new("unexpected HTTP #{response.status}", model: operation_id, field: "$", **metadata.slice(:request_id, :idempotency_key))
@@ -29,9 +29,33 @@ module FiscalRail
29
29
  end
30
30
 
31
31
  class AccountTaxRegimes < Resource
32
+ def es
33
+ @es ||= SpanishSubmission.new(@transport)
34
+ end
35
+
32
36
  def retrieve
33
37
  request("retrieveAccountTaxRegime", retry_safe: true)
34
38
  end
35
39
  end
40
+ class SpanishSubmission < Resource
41
+ def upload_certificate(certificate_file:, certificate_password: nil)
42
+ content = certificate_file.read(128 * 1024 + 1) || ""
43
+ raise ArgumentError, "certificate_file must be at most 128 KiB" if content.bytesize > 128 * 1024
44
+
45
+ request("uploadAccountCertificate", multipart: { certificate_file: content, certificate_password: certificate_password })
46
+ end
47
+
48
+ def verify_representation
49
+ request("verifyAccountRepresentation")
50
+ end
51
+
52
+ def verify_submission
53
+ request("verifyAccountSubmission")
54
+ end
55
+
56
+ def cancel_submission_change
57
+ request("cancelAccountSubmissionChange")
58
+ end
59
+ end
36
60
  end
37
61
  end
@@ -4,6 +4,7 @@ require "net/http"
4
4
  require "json"
5
5
  require "uri"
6
6
  require "thread"
7
+ require "securerandom"
7
8
 
8
9
  module FiscalRail
9
10
  # The adapter boundary also makes custom proxy/TLS/instrumentation possible.
@@ -63,7 +64,7 @@ module FiscalRail
63
64
  @api_key, @base_url, @max_retries, @adapter, @sleeper = api_key, base_url.sub(%r{/+\z}, ""), max_retries, adapter, sleeper
64
65
  end
65
66
 
66
- def request(method:, path:, body: nil, params: {}, headers: {}, retry_safe:, idempotency_key: nil)
67
+ def request(method:, path:, body: nil, multipart: nil, params: {}, headers: {}, retry_safe:, idempotency_key: nil)
67
68
  uri = URI("#{@base_url}#{path}")
68
69
  query = Serialization.json_value(params.reject { |_, value| value.nil? })
69
70
  uri.query = URI.encode_www_form(query) unless query.empty?
@@ -74,6 +75,17 @@ module FiscalRail
74
75
  request_headers["Idempotency-Key"] = idempotency_key if idempotency_key
75
76
  request_headers["Content-Type"] = "application/json" unless body.nil?
76
77
  encoded = body.nil? ? nil : JSON.generate(Serialization.json_value(body))
78
+ if multipart
79
+ boundary = "fiscalrail-#{SecureRandom.hex(24)}"
80
+ request_headers["Content-Type"] = "multipart/form-data; boundary=#{boundary}"
81
+ encoded = "--#{boundary}\r\nContent-Disposition: form-data; name=\"certificate_file\"; filename=\"certificate.p12\"\r\nContent-Type: application/x-pkcs12\r\n\r\n".b
82
+ encoded << multipart.fetch(:certificate_file).b << "\r\n".b
83
+ unless multipart[:certificate_password].nil?
84
+ encoded << "--#{boundary}\r\nContent-Disposition: form-data; name=\"certificate_password\"\r\n\r\n".b
85
+ encoded << multipart.fetch(:certificate_password).b << "\r\n".b
86
+ end
87
+ encoded << "--#{boundary}--\r\n".b
88
+ end
77
89
  attempt = 0
78
90
  loop do
79
91
  begin
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FiscalRail
4
- VERSION = "0.5.0"
4
+ VERSION = "0.6.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fiscalrail
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - FiscalRail