einvoice 1.4.0 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2393870fedb2a46c8a29d7c903b109080499b621904e46d45b505c33e8e3147c
4
- data.tar.gz: c8ad7c8e9e6674887af0458bc00ed362e182ad39a2d30d7a4624f537e96e1c0d
3
+ metadata.gz: 0ce2293f378b646e5ae9ab94d5cce8b539a7dd441d8db03472ae3c6f4bbd9735
4
+ data.tar.gz: 0d8e775b71d10cb37f3892cc40bd8e46e1ce872721ec7cb1c0864807d6a5f24d
5
5
  SHA512:
6
- metadata.gz: d44581204a25dcd2f434510535ae5d5803263310ecbb6431590c5c9bc7398a10e63c0d9391c12d86417a2040cc5031c884fba915e7015c394ff32e14ed880801
7
- data.tar.gz: 4076e9ef62d58aba07a0261dfee0b989d58a6eb41ade8b582e1770fde2f1f709e7a6308260a6b3312221454b1e97651489343cfc63beba4d8e86d9189868a10e
6
+ metadata.gz: 233c9f4194869b5e61420c7b255f768befe0f09cb3f952f1c300c7d9b8e417d98053461f1b794cf5f7a9a7c4b4a428b38b99db0005e77429d9686bf3afad1bda
7
+ data.tar.gz: 11f5ba7e0132eb0dc9eb4d0fc16cd48252b950b3c189f2c45efa4757bfbc43936c3c97155692d2e3b989bf038e528e20f5ee023031817fed9ab0a3a7bbb564d7
data/.coderabbit.yaml ADDED
@@ -0,0 +1,14 @@
1
+ reviews:
2
+ profile: chill
3
+ request_changes_workflow: false
4
+ auto_review:
5
+ enabled: true
6
+ drafts: false
7
+ path_filters:
8
+ - "!lib/einvoice/donation_unit_list.json"
9
+ path_instructions:
10
+ - path: "lib/einvoice/tradevan/**"
11
+ instructions: >
12
+ This is legacy 1.x code kept for backward compatibility.
13
+ Do not suggest large-scale refactors or architecture changes here.
14
+ Focus only on correctness bugs and security issues.
@@ -2,9 +2,9 @@ name: build
2
2
 
3
3
  on:
4
4
  push:
5
- branches: [ main ]
5
+ branches: [ 1-x-stable ]
6
6
  pull_request:
7
- branches: [ main ]
7
+ branches: [ 1-x-stable ]
8
8
 
9
9
  permissions:
10
10
  contents: read
@@ -16,7 +16,7 @@ jobs:
16
16
  ruby-version: ['3.1', '3.2', '3.3', '3.4']
17
17
  runs-on: ubuntu-latest
18
18
  steps:
19
- - uses: actions/checkout@v3
19
+ - uses: actions/checkout@v4
20
20
  - name: Set up Ruby
21
21
  uses: ruby/setup-ruby@v1
22
22
  with:
data/CHANGELOG.md CHANGED
@@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
+ ## [1.5.0] - 2026-09-11
9
+
10
+ ### Fixed
11
+ - Tradevan `IssueData#payload` and `VoidData#payload` now exclude
12
+ `context_for_validation`. On Rails 8 this internal ivar was sent as a
13
+ business field once `valid?` had run, and Tradevan rejected the whole
14
+ request. Thanks @dlackty.
15
+
16
+ ### Changed
17
+ - **Ruby 3.1 or newer is now required.** CI covers 3.1 through 3.4.
18
+ - **Faraday 2 is now required** (`~> 2.1`); `faraday_middleware` is gone,
19
+ replaced by a built-in `Faraday::Response::ParseXml`. If you pin
20
+ `faraday` to 1.x, stay on 1.4.0.
21
+ - `Einvoice::Utils` and the `gyoku` dependency have been removed. Nothing
22
+ in the gem called them.
23
+
24
+ ### Internal
25
+ - Added request-level Tradevan provider specs.
26
+ - CI runs on `1-x-stable`; the donation unit list task parses with `CSV`.
27
+ - README documents the allowance operations and 1.x maintenance mode.
28
+
8
29
  ## [1.4.0] - 2026-08-08
9
30
 
10
31
  ### Changed
data/Gemfile CHANGED
@@ -3,6 +3,14 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in einvoice.gemspec
4
4
  gemspec
5
5
 
6
+ # Remove this pin once we drop Ruby 3.1 support. json 3.0 dropped quirks_mode,
7
+ # which activesupport 7.2.3.2 still passes; 7.2.3.2 is the newest activesupport
8
+ # that works on Ruby 3.1.
9
+ gem "json", "< 3.0"
10
+
11
+ # Used by the update_donation_unit rake task; csv leaves the default gems in Ruby 3.4.
12
+ gem "csv", require: false, group: :development
13
+
6
14
  gem "ostruct", require: false, group: :development
7
15
  gem "simplecov", require: false, group: :test
8
16
  gem "simplecov-lcov", require: false, group: :test
data/README.md CHANGED
@@ -2,6 +2,11 @@
2
2
  [![Build Status](https://github.com/abookyun/einvoice/actions/workflows/build.yml/badge.svg)](https://github.com/abookyun/einvoice/actions/workflows/build.yml)
3
3
  [![codecov](https://codecov.io/github/abookyun/einvoice/graph/badge.svg?token=yxJNmXiUyp)](https://codecov.io/github/abookyun/einvoice)
4
4
 
5
+ > **The 1.x line is moving to maintenance mode.** A provider-agnostic 2.0
6
+ > rewrite is in progress on `main`; once it lands, 1.x continues on the
7
+ > [`1-x-stable`](https://github.com/abookyun/einvoice/tree/1-x-stable) branch.
8
+ > If you depend on the current Tradevan client, pin `~> 1.4`.
9
+
5
10
  ## What's E-Invoice
6
11
 
7
12
  To support the thriving e-commerce industry and lower the business costs and barriers associated with printing paper receipts, the Taiwan Executive Yuan announced plans in August 2000 to implement electronic receipts in Taiwan and launched a comprehensive project in May 2010 to promote e-invoice applications. This initiative employs innovative approaches such as allowing consumers to claim virtual receipts via multiple devices, offering automatic checking of receipt lottery numbers, and providing a variety of channels for retailers to issue receipts.
@@ -67,7 +72,7 @@ Einvoice::Tradevan::Provider.new(ssl_verify: false)
67
72
  client = Einvoice::Client.new(Einvoice::Tradevan::Provider.new)
68
73
  ```
69
74
 
70
- ### Issue an invoice
75
+ ### Issue an invoice (開立電子發票)
71
76
 
72
77
  ```ruby
73
78
  payload = {
@@ -138,7 +143,7 @@ result.data
138
143
  # }
139
144
  ```
140
145
 
141
- ### Cancel an invoice
146
+ ### Cancel an invoice (作廢電子發票)
142
147
 
143
148
  ```ruby
144
149
  payload = {
@@ -165,7 +170,74 @@ result.data
165
170
  # }
166
171
  ```
167
172
 
168
- ### Search invoice by memberId/memberId/sellTargetCode
173
+ ### Issue an allowance (開立折讓單)
174
+
175
+ An allowance against a previously issued invoice is sent through `issue` with `type: "A"`. Each item must reference the original invoice (`invoiceNumber` / `invoiceDate`) and carry its tax-exclusive amount (`itemExclude`).
176
+
177
+ ```ruby
178
+ payload = {
179
+ companyUn: "12345678",
180
+ orgId: "ABCDE",
181
+ type: "A",
182
+ allowanceIdentifier: "12345678_ABCDE_53b2a44e4b3d",
183
+ transactionNumber: "53b2a44e4b3d",
184
+ transactionDate: "20160426",
185
+ paperPrintMode: "0",
186
+ invoiceAlarmMode: "4",
187
+ allowanceExclusiveAmount: "95",
188
+ allowanceTax: "5",
189
+ allowanceInclusiveAmount: "100",
190
+ allowancePaperReturned: "N",
191
+ invoicePaperReturned: "N",
192
+ receiverName: "John Appleseed",
193
+ receiverEmail: "john@gmail.com",
194
+ itemList: [
195
+ {
196
+ saleIdentifier: "12345678_ABCDE_53b2a44e4b3c",
197
+ serialNumber: "0001",
198
+ invoiceNumber: "GX38551078",
199
+ invoiceDate: "20160425",
200
+ productName: "Coffee Latte",
201
+ qty: "1000",
202
+ price: "100",
203
+ itemExclude: "95",
204
+ tax: "5",
205
+ itemTotal: "100",
206
+ taxType: "T"
207
+ }
208
+ ]
209
+ }
210
+ ```
211
+
212
+ ```ruby
213
+ result = client.issue(payload)
214
+ result.successful?
215
+ #=> true
216
+ ```
217
+
218
+ Notes:
219
+
220
+ * `allowanceIdentifier` must be prefixed with `"#{companyUn}_#{orgId}_"`, like `saleIdentifier` on invoices.
221
+ * `allowanceExclusiveAmount` + `allowanceTax` = `allowanceInclusiveAmount`.
222
+
223
+ ### Void an allowance (作廢折讓單)
224
+
225
+ ```ruby
226
+ payload = {
227
+ type: "A",
228
+ companyUn: "12345678",
229
+ allowanceNumber: "ABCDE20160426001",
230
+ allowancePaperReturned: "Y",
231
+ }
232
+ ```
233
+
234
+ ```ruby
235
+ result = client.cancel(payload)
236
+ result.successful?
237
+ #=> true
238
+ ```
239
+
240
+ ### Search invoice by memberId/sellTargetCode (載具/會員發票查詢)
169
241
 
170
242
  ```ruby
171
243
  payload = {
@@ -211,7 +283,7 @@ result.data
211
283
  # ]
212
284
  ```
213
285
 
214
- ### Search invoice detail
286
+ ### Search invoice detail (發票開立明細查詢)
215
287
 
216
288
  ```ruby
217
289
  result = client.search_invoice_detail("GX38551078")
@@ -255,7 +327,7 @@ result.data
255
327
  # }
256
328
  ```
257
329
 
258
- ### Send card info to customer
330
+ ### Send card info to customer (電子發票載具歸戶/中獎通知作業)
259
331
 
260
332
  ```ruby
261
333
  payload = {
@@ -275,7 +347,7 @@ result.successful?
275
347
  result.data
276
348
  ```
277
349
 
278
- ### Get invoice mark info
350
+ ### Get invoice mark info (電子發票字軌號碼配額取號/分配作業)
279
351
 
280
352
  ```ruby
281
353
  payload = {
@@ -307,13 +379,13 @@ result.data
307
379
  # ]
308
380
  ```
309
381
 
310
- ### Get donate unit list
382
+ ### Get donate unit list (受贈機關/社福團體愛心碼清單查詢)
311
383
 
312
384
  ```ruby
313
385
  client.get_donate_unit_list("12345678")
314
386
  ```
315
387
 
316
- ### Get invoice content
388
+ ### Get invoice content (電子發票內容查詢)
317
389
 
318
390
  ```ruby
319
391
  payload = {
data/Rakefile CHANGED
@@ -7,13 +7,51 @@ task default: :spec
7
7
 
8
8
  desc "Download latest e-invoice donation unit list and convert it to JSON"
9
9
  task :update_donation_unit do
10
+ require "csv"
10
11
  require "json"
11
12
  require "faraday"
13
+
12
14
  # From https://data.gov.tw/dataset/31868
13
- data = Faraday.get("https://dataset.einvoice.nat.gov.tw/ods/portal/ODS303W/download/3886F055-EB77-4DF9-98E2-F3F49A7D3434/1/8B227A99-042A-4903-8B34-5715442A227D/0/?fileType=csv").body.force_encoding("utf-8")
14
- orgs = data.split("\n")[1..-1].map do |line|
15
- line.split(",").values_at(2, 1)
16
- end.sort_by(&:first)
15
+ url = "https://dataset.einvoice.nat.gov.tw/ods/portal/ODS303W/download/3886F055-EB77-4DF9-98E2-F3F49A7D3434/1/8B227A99-042A-4903-8B34-5715442A227D/0/?fileType=csv"
16
+ response = Faraday.get(url)
17
+ raise "Download failed: HTTP #{response.status}" unless response.status == 200
18
+
19
+ body = response.body
20
+
21
+ possible = ["UTF-8", "Big5"]
22
+ chosen = nil
23
+ utf8_text = nil
24
+
25
+ possible.each do |code|
26
+ begin
27
+ str = body.dup.force_encoding(code)
28
+ candidate = str.encode("UTF-8")
29
+ if candidate.valid_encoding?
30
+ chosen = code
31
+ utf8_text = candidate
32
+ break
33
+ end
34
+ rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError
35
+ next
36
+ end
37
+ end
38
+
39
+ unless chosen
40
+ chosen = "fallback(replace)"
41
+ utf8_text = body.dup.force_encoding("UTF-8").scrub("?")
42
+ puts "::warning::Used fallback encoding with replacement; some characters may be lost"
43
+ end
44
+
45
+ utf8_text = utf8_text.delete_prefix("\uFEFF") # remove possible UTF-8 BOM
46
+ csv = CSV.parse(utf8_text, headers: true)
47
+
48
+ orgs = csv.map do |row|
49
+ code = (row['捐贈碼'] || row[2]).to_s.strip
50
+ name = (row['受捐贈機關或團體名稱'] || row[1]).to_s.strip
51
+ [code, name]
52
+ end.sort_by { |code, _| [code.to_i, code] }
53
+
54
+ File.write "lib/einvoice/donation_unit_list.json", JSON.pretty_generate(orgs.to_h)
17
55
 
18
- File.write "lib/einvoice/donation_unit_list.json", JSON.pretty_generate(Hash[orgs])
19
- end
56
+ puts "Wrote lib/einvoice/donation_unit_list.json (#{orgs.length} entries)"
57
+ end
data/einvoice.gemspec CHANGED
@@ -9,25 +9,30 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ["David Yun"]
10
10
  spec.email = ["abookyun@gmail.com"]
11
11
 
12
- spec.summary = %q{A API wrapper for Taiwan e-invoice services.}
12
+ spec.summary = %q{An API wrapper for Taiwan e-invoice services.}
13
13
  spec.homepage = "https://github.com/abookyun/einvoice"
14
14
  spec.license = "MIT"
15
15
 
16
+ spec.metadata = {
17
+ "source_code_uri" => "https://github.com/abookyun/einvoice",
18
+ "changelog_uri" => "https://github.com/abookyun/einvoice/blob/main/CHANGELOG.md",
19
+ "bug_tracker_uri" => "https://github.com/abookyun/einvoice/issues",
20
+ "rubygems_mfa_required" => "true"
21
+ }
22
+
16
23
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
24
  spec.bindir = "exe"
18
25
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
26
  spec.require_paths = ["lib"]
20
27
 
21
- spec.required_ruby_version = '>= 2.0.0'
22
- spec.add_dependency "faraday"
23
- spec.add_dependency "faraday_middleware"
28
+ spec.required_ruby_version = '>= 3.1'
29
+ spec.add_dependency "faraday", "~> 2.1"
24
30
  spec.add_dependency "multi_xml"
25
31
  spec.add_dependency "activemodel"
26
32
  spec.add_dependency "activesupport"
27
- spec.add_dependency "gyoku"
28
33
 
29
34
  spec.add_development_dependency "bundler", "> 1.17"
30
- spec.add_development_dependency "rake", "~> 12.3"
35
+ spec.add_development_dependency "rake", "~> 13.0"
31
36
  spec.add_development_dependency "pry"
32
37
  spec.add_development_dependency "rspec"
33
38
  spec.add_development_dependency "factory_bot"
@@ -20,7 +20,7 @@ module Einvoice
20
20
  DEFAULT_ENCRYPTION_KEYS = {}
21
21
  DEFAULT_SSL_VERIFY = true
22
22
 
23
- attr_accessor *VALID_OPTIONS_KEYS
23
+ attr_accessor(*VALID_OPTIONS_KEYS)
24
24
 
25
25
  def self.extended(base)
26
26
  base.reset
@@ -1,5 +1,5 @@
1
- require "faraday_middleware"
2
1
  require "faraday/response/decode_tradevan"
2
+ require "faraday/response/parse_xml"
3
3
 
4
4
  module Einvoice
5
5
  module Connection